OAuth Provider Setup
Add a new OAuth provider to your registry with platform approval and connection flows.
Installation
- Make sure Claude is on your device and in your terminal.
Skills load from
~/.claude/skills/when Claude Code starts up β so you need it on your machine first. If you don't have it yet, install it once with the command below, then runclaudein any terminal to verify.One-time setupnpm i -g @anthropic-ai/claude-codeAlready have it? Skip ahead.
- Paste into Claude Code or into your terminal.
This copies the whole skill folder into
~/.claude/skills/add-oauth-provider-superdesigndev/β the SKILL.md plus any scripts, reference docs, or templates the skill ships with. Safe default: works for every skill.Faster alternative (instruction-only skills)
Skips the clone and grabs only the SKILL.md file. Don't use this if the skill ships Python scripts, reference markdowns, or asset templates β they won't be downloaded and the skill will fail when it tries to load them.
Quick install (SKILL.md only)Sign up to copy - Restart Claude Code.
Quit and reopen Claude Code (or any other agent that loads from
~/.claude/skills/). New skills are picked up on startup. - Just ask Claude.
Skills auto-activate when your request matches the skill's description β no slash command needed. Trigger phrases live in the skill's own frontmatter; you can read them in the βWhat this skill doesβ section above.
Prefer to read the source first? Open on GitHub.
When Claude uses it
Add a provider to treg's OAuth registry (the ones treg holds its own approved app for). Use when asked to "add YouTube/Notion/Meta OAuth", "support connecting X", "add a new OAuth provider", or when a connect flow, capability picker, channel/account picker, or provider health probe needs building. Covers the code changes, the platform-side approval steps, and the pitfalls that don't announce themselves.
What this skill does
Adding an OAuth provider
Two connect modes exist. BYO (POST /oauth/start with a caller-supplied
client_id/secret) already works for any OAuth2 provider and needs no code. This
skill is the registry path: treg owns the registered app, so the user picks a
provider and supplies nothing. Only add a provider here if treg holds β or is
willing to go get β the platform approval behind it.
Read src/treg/oauth_providers.py's module docstring first. The rule it states is
the one most easily broken: scopes are per capability, never per provider.
The code changes
All of these, in order. Skipping any one produces a provider that looks fine in
/oauth/providers and fails somewhere the tests don't reach.
1. Credentials β src/treg/config.py
Add <name>_client_id / <name>_client_secret to Settings, loaded from
TREG_<NAME>_CLIENT_ID / _SECRET. Reuse an existing pair when the platform
uses one app for several products β all four Google providers plus YouTube
share google_client_id, because Google verification and API audits are scoped to
the Cloud project, not to the OAuth client. A second client in the same project
isolates nothing.
2. The provider β src/treg/oauth_providers.py
Add the OAuthProvider and register it in REGISTRY (easy to forget; the
provider silently doesn't exist until you do).
Capabilities are cumulative supersets, never swaps:
scopes={
"read": [READ_SCOPES],
"post": [*READ_SCOPES, UPLOAD], # post CONTAINS read
"manage": [*READ_SCOPES, UPLOAD, WRITE], # manage CONTAINS post
}
satisfied_capabilities() is set-containment, so a non-cumulative write yields a
connection that can write but reports "no read". default_capability is the
broadest (most scopes) β deliberately, see its docstring.
Split a capability whenever the platform splits the scope. YouTube needs
read/post/manage because uploading a video and being able to edit or delete
one are different Google scopes; collapsing them means a connection that can post
and then never fix a typo.
Per-provider quirks worth knowing: auth_params={} for providers that reject
Google's access_type/prompt (LinkedIn, Slack, X), pkce=True +
token_endpoint_auth_method="client_secret_basic" for X, extra_credential_* when
a second credential rides along (Google Ads' developer token).
3. Discovery β "which account does this connection act on?"
Set discover_path / discover_key / discover_id_field / discover_label_field.
- Label and id fields are dotted paths (
_dig), so nested values work:discover_label_field="snippet.title". discover_base_urlwhen listing lives on a different host than the data API (GA4 reports come from analyticsdata, properties from analyticsadmin).discover_nested_keywhen the rows are nested one level down.enrich_*when the listing returns bare ids and a second call is needed for a human name (Google Ads).- Query strings in
discover_pathare fine β discovery does not passparams.
Leave it unset if the credential acts on the whole account; the UI then says "whole account" rather than showing an empty picker.
4. probe_path β health + the Tools "Use" prefill
A cheap authenticated GET on base_url (not discover_base_url β the probe
runs against the provisioned tool's own host). Without it the tool reads
"unchecked" forever and the Try panel opens blank.
Prefer the path that returns a human-recognisable field, and keep it identical to
the sample path in step 5 β openUse prefers health_check.path over the sample
map, so if they differ the prefill silently changes after a reconnect.
5. Dashboard β src/treg/web/index.html
Provider rows, the Connect button and the pickers are all data-driven from
listing(). No new provider needs a UI change β except:
- A new capability name must be added to
capLabel/capHelp(~line 2762). An unknown name renders as the bare key plus "Requests the x scopes." samplePath(~line 3024) is a hardcoded hostβpath map used when a tool has nohealth_checkyet. Add the host so the Try panel prefills for tools that were provisioned before the probe existed.
6. Tests
tests/test_oauth_providers_m3.py::test_every_provider_is_registered asserts an
exact set of service names. Add yours or the suite fails.
Then: uv run --with pytest-xdist pytest -n auto -q (full suite β the registry
touches health, tools and connections).
The platform side (the actual long pole)
The code is an afternoon. Approval is weeks. Do these in parallel, not in series.
- Enable the APIs on the project before touching the consent screen β scope pickers usually only list scopes belonging to enabled APIs.
- Add the scopes. Ask only for what you can demonstrate; see pitfalls.
- Redirect URI must match
{TREG_PUBLIC_URL}/oauth/callbackexactly. - Verification / app review, where the platform gates sensitive scopes.
- Separate product audits β these are extra and often slower than the OAuth review: YouTube's compliance audit + quota extension, Business Profile API access, the Google Ads developer token, LinkedIn's Community Management API.
For the verification submission
- App name, homepage, privacy policy and demo video must all agree. The consent-screen brand is what reviewers compare everything against β a mismatch is a routine rejection.
- The privacy policy must name the platform's data explicitly and link the platform's ToS plus the provider's privacy policy.
- The demo video must show each requested scope actually in use, with the client id visible in the consent URL. This is the real constraint on scope count β see the first pitfall.
Pitfalls
Each of these cost real time.
Never request a scope you can't film. "Requested scopes exceed what the demo
shows" is the standard rejection. YouTube's youtubepartner needs a YouTube CMS
account to make any call at all, so an app without one literally cannot produce
the evidence. Copying a scope list off another product imports approvals it holds
and you don't β ask for what your own demo can show, and add the rest later.
A short-lived token with no refresh_token is a connection that is already dead. Meta's
authorization-code exchange returns a 1-2 hour user token and never issues a refresh_token, so
a provider added without long_lived_exchange=True produces connections that break before anyone
uses them β the connect succeeds, the probe passes, the tool 401s that afternoon. The extra
grant_type=fb_exchange_token call buys ~60 days, which is the ceiling for a Meta user token; it
still can't renew unattended, so it surfaces through needs_reconnect like LinkedIn rather than
pretending to auto-heal. Before trusting any new provider, read what its token endpoint actually
returned: expires_in absent does not mean "never expires", and the 3600s default in
exchange_code will happily invent an expiry that isn't real.
Publishing status "Testing" expires refresh tokens after 7 days. Every connection then breaks weekly β fatal for treg, and invisible until day 8. Confirm the app is In production before trusting any connection made during setup.
The "hasn't verified this app" interstitial is expected pre-approval. Click through via Advanced. Demo videos are necessarily filmed in that state.
Scope pickers filter on the scope URL, not the product name.
yt-analytics.readonly does not contain the string "youtube". Use the "manually
add scopes" box when the picker won't cooperate.
TREG_PUBLIC_URL decides the redirect_uri, not the port you're browsing.
Running two dev servers and connecting from the wrong one sends the callback to
the other server, where your session cookie doesn't apply.
health_check attaches at connect time. Adding a probe_path does nothing for
already-connected tools until they reconnect (the existing-tool branch of
_autoprovision_provider_tool backfills it).
httpx replaces a URL's query string whenever params is passed β even [].
_probe merges with copy_add_param for exactly this reason; don't "simplify" it
back, or any probe path carrying its own query gets truncated and reports a healthy
credential as invalid. Covered by
test_probe_preserves_a_query_string_in_the_probe_path.
.ttable .tn is white-space:nowrap, and .ttable-wrap is overflow:hidden.
Long text in a .tn cell widens the table past the modal and clips the action
button off-screen rather than scrolling to it β the row becomes unclickable. The
capability modal deliberately does not use .tn.
Test upstreams must be defined at module scope. Test files use
from __future__ import annotations, so FastAPI resolves a route's Request
annotation from module globals; a route declared inside a test function 422s
instead of matching.
Verify before calling it done
uv run --with pytest-xdist pytest -n auto -q # full suite
scripts/dev-local.sh up # see .agents/skills/dev-local
Then, in the dashboard: Connect β capability picker reads in plain English β
consent β account/channel picker shows real names (not bare ids) β #tools β Use β
Send returns live data. Only the last step proves the credential, the binding, the
proxy and the base URL are all correct together.
Related skills
Generative Code Art
anthropics
Create algorithmic art with p5.js using randomness and interactive parameters.
Poster & Visual Design
anthropics
Create original posters and visual art in PNG and PDF formats.
Claude API Helper
anthropics
Build, debug, and optimize Claude API applications with caching and model migration support.
MCP Server Builder
anthropics
Build protocol servers that connect language models to external APIs and services.