Skip to content

Let users add remote MCP servers by URL as their own connectors - #2

Merged
idoubi merged 3 commits into
mainfrom
feat/custom-mcp
Oct 9, 2026
Merged

idoubi merged 3 commits into
mainfrom
feat/custom-mcp

Conversation

@idoubi

@idoubi idoubi commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Summary

A project's user can now add a remote MCP server that is not in the catalog, by its URL. It becomes a connector named mcp_<10 hex> that only that user can see, connect and call; authorization, tool listing, calls and disconnect use the existing connector APIs.

  • POST /v1/custom-connectors { external_user_id, url }: discovers the server's sign-in per the MCP authorization spec — an unauthenticated initialize returns 401, then protected resource metadata (RFC 9728), then authorization server metadata (RFC 8414) — and registers a client (RFC 7591). Only OAuth servers with PKCE S256 are accepted; servers without sign-in or with API keys only are refused with closed error codes. Adding the same URL again returns the same connector.
  • GET /v1/custom-connectors?external_user_id=… and DELETE /v1/custom-connectors/{name}?external_user_id=… (revokes and deletes the user's connections, sessions, tool catalog and client).
  • Custom connectors never appear in GET /v1/connectors, GET /v1/tools or the console; other users or projects get connector_not_found. At most 20 per user. Deleting a project removes them.
  • Every request to such a server goes through a guarded fetch: https only, no credentials in the URL, the host must resolve only to public addresses (loopback, private ranges, link-local/cloud metadata, CGNAT, mapped IPv6 and NAT64 forms are refused), and redirects are never followed. The protected resource metadata must name a resource on the server's own origin.
  • Definitions live in the new custom_connectors table (migration 015) and are loaded into the catalog lookup on first use, so every process can serve them.
  • CUSTOM_MCP_ALLOW_FAKE_IP=true (development only) accepts 198.18.0.0/15 for machines behind a fake-IP proxy.
  • API docs (zh / en), SDK methods and docs/custom-connectors.md (design, security, known limits).

Known limit, documented: the address is checked when each request starts, so a DNS rebinding between the check and the connection is theoretically possible; pinning the resolved address in the connection layer would close it.

Test plan

  • npm run typecheck
  • npm test — 64 pass, incl. tests/custom-connectors.test.ts (address checks, discovery and its refusals, authorization and calls only through the guarded fetcher)
  • npm run test:integration — 33 pass, incl. tests/integration/custom.test.ts (add, idempotent re-add, connect, call, reload of the definition in a fresh process, isolation across users and projects, kept out of shared catalogs, removal and project deletion cleanup)
  • Discovery against real servers: FeedShine, Linear and Notion resolve to the same endpoints as their catalog entries; localhost is refused
  • Running locally on port 3200 against the local database; /health and GET /v1/custom-connectors respond

🤖 Generated with Claude Code

idoubi and others added 3 commits October 9, 2026 15:59
POST /v1/custom-connectors discovers the server's OAuth setup (RFC 9728, RFC 8414),
registers a client (RFC 7591) and records a connector named mcp_<hash> that only that
project user can see, connect and call. Every request to such a server is checked to reach
public addresses only and never follows redirects. Listing and removal included; deleting a
project removes its custom servers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CUSTOM_MCP_ALLOW_FAKE_IP=true accepts 198.18.0.0/15, which proxies in fake-IP mode
answer every name with. Off by default; never for servers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps the Google REST connectors and zh-HK locales from main; custom MCP servers stay
out of the shared tool catalog, which now also lists built-in REST tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@idoubi
idoubi merged commit afcb854 into main Oct 9, 2026
4 checks passed
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