Skip to content

PUBLISH P3: remote claude.ai connector (Streamable-HTTP + OAuth 2.1) - #38

Merged
thorwhalen merged 2 commits into
mainfrom
claude/remote-connector
Jun 17, 2026
Merged

PUBLISH P3: remote claude.ai connector (Streamable-HTTP + OAuth 2.1)#38
thorwhalen merged 2 commits into
mainfrom
claude/remote-connector

Conversation

@thorwhalen

Copy link
Copy Markdown
Owner

What

Adds the remote claude.ai connector publish target (DECISIONS D19). A
claude.ai custom connector is a remote MCP server reached from Anthropic's
cloud over HTTPS + OAuth 2.1 — a separate surface from the local stdio .mcpb.
claude-remote-connector (coact/publish_remote.py) scaffolds a deployable
service
you host.

coact publish mypkg.tools:summarize --target claude-remote-connector \
  --name my-conn --dest ./out \
  --connector-url https://my-conn.example.com --idp-issuer https://my-idp.example.com
# → out/my-conn-connector/  (server/app.py, connector_config.json, requirements.txt, DEPLOY.md, Dockerfile)

Design

  • The artifact is a deploy scaffold, not a file — a hosted service (you own the deploy), mirroring how .mcpb packs a bundle (you own the install). D8/D13.
  • coact writes packaging; py2mcp serves the MCP (D17). The Streamable-HTTP + OAuth wiring is py2mcp's http.mk_http_app/serve_http (added upstream — py2mcp#4, merged, 0.1.4), wrapping FastMCP. Building the scaffold is pure stdlib; py2mcp/fastmcp/uvicorn are runtime-only (missing → warning). import coact pulls in none of them (isolation-tested).
  • Resource-server OAuth by construction (landscape §4.4/§8.5): emitted auth is type: jwt — validate a managed IdP's JWTs (JWTVerifierRemoteAuthProvider), audience-bound (RFC 8707), never an authorization server, never forwards the inbound token upstream. DEPLOY.md states the rules.
  • No silent unauthenticated config: missing IdP/connector-url → clearly-marked placeholders + a loud warning (a remote connector MUST require OAuth). IntegrationSpec.auth='oauth2.1'/deployment='remote-http' go live; same draft/empty/unsafe-name guards as .mcpb.

Tests

405 passed, 4 skipped; ruff clean. 12 offline tests assert the emitted files; a dev-only test (skipped in bare CI) execs the generated server/app.py to prove it builds a real authed ASGI app. Reviewed by an adversarial security-focused multi-agent pass over the OAuth wiring.

The second PUBLISH target (D19): a claude.ai *custom connector* is a REMOTE MCP
server reached from Anthropic's cloud over HTTPS + OAuth 2.1 — a separate surface
from the local stdio .mcpb. `claude-remote-connector` (coact/publish_remote.py)
SCAFFOLDS a deployable service you host:

- emits server/app.py (an ASGI app via py2mcp.http.mk_http_app), a
  connector_config.json, requirements.txt, a DEPLOY.md guide, and a Dockerfile.
- the auth block is a resource-server (type=jwt): validate a managed IdP's JWTs,
  audience-bound (RFC 8707), never issue tokens, never forward them upstream.
- no IdP/connector-url given -> clearly-marked placeholders + a loud warning
  (never a silently-unauthenticated config); a remote connector MUST require OAuth.
- coact writes packaging; py2mcp.http builds + serves the MCP server (D17). The
  build is pure stdlib; py2mcp/fastmcp/uvicorn are runtime-only (missing -> warning).
  `import coact` pulls in none of them (isolation-tested).
- IntegrationSpec.auth='oauth2.1' / deployment='remote-http' go live; same draft/
  empty guards as .mcpb.

CLI `coact publish --target claude-remote-connector [--connector-url --idp-issuer]`;
exports publish_remote; DECISIONS D19; skill + README updated. 12 offline tests
(one dev-only test execs the generated app.py to prove it builds a real authed ASGI
app). The py2mcp http layer has its own upstream PR. Full suite: 405 passed.
Adversarial security review (8 confirmed / 8 refuted); all confirmed fixed:

- [high] CLI --connector-url/--idp-issuer were forwarded unconditionally and
  crashed the default claude-local-mcpb target with a raw TypeError. They now
  raise a clear error unless --target claude-remote-connector.
- [high] upstream py2mcp fix (separate PR): JWTVerifier skips audience validation
  when audience is None; mk_auth_provider now REQUIRES audience (RFC 8707, the
  confused-deputy defense — fail-closed). coact always supplies one, but the pin
  is bumped accordingly.
- [low] a tools-less spec (resources/prompts only) now gets its own clear message
  instead of a misleading "0 proposed tool(s)" design-draft message.
- [low] scaffold pins py2mcp>=0.1.5 (the release that actually ships py2mcp.http;
  0.1.4 predated it) in all three places.
- tests added: CLI flag routing (reject-on-wrong-target + forward-on-remote),
  the py2mcp-absent warning branch, placeholder audience-binding content, and
  preview truncation.

Full suite: 410 passed, 4 skipped; ruff clean.
@thorwhalen
thorwhalen merged commit fecee2f into main Jun 17, 2026
12 checks passed
@thorwhalen
thorwhalen deleted the claude/remote-connector branch June 17, 2026 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant