User-configured MCP Connectors with OAuth 2.1
nforum lets end users register Streamable-HTTP MCP servers from the account settings page and expose those tools to the chat agent. The three-commit feature covers the data model, a polished UI, and a full OAuth 2.1 flow with PKCE and dynamic client registration.
The foundation is a user_mcp_servers table with Postgres RLS (owner-only, four policies) and a headers jsonb column for bearer tokens. Tools discovered from registered servers are merged into the per-request tool list and routed to the LLM under the mcp__<slug>__<tool> prefix. A /user/mcp-servers CRUD router includes a /test probe that connects and lists available tools. The schema conversion from MCP inputSchema to the app's OpenAIToolSchema enforces a 64-character tool-name limit the spec requires.
The polish commit renames the settings tab to "Connectors" and adds a chat-input toggle per registered server. A new mcp_tool_result SSE event carries { server, tool, ok, args, output } with args and output capped at 4 KB for the stored preview; the model still receives full untruncated output. The UI renders Server · tool_name so the raw mcp__<slug>__<tool> string doesn't surface to users.
The third commit adds OAuth 2.1 via the MCP SDK's auth() helper, which handles RFC 9728 metadata discovery, RFC 7591 dynamic client registration, PKCE (S256), and token refresh. State across the popup hop is HMAC-signed with a 5-minute TTL - no server-side session needed. New DB columns auth_type, oauth_metadata, oauth_tokens, and oauth_code_verifier land via migration 002.
Before pulling: tokens and headers are plaintext in jsonb under RLS only; per-row encryption is explicitly deferred. The new mcp__<slug>__<tool> tool namespace interacts with the security-hardening commit that added TOOL USE BOUNDARIES to the system prompt - both land in the same window and should be read together. BACKEND_PUBLIC_URL must be set in any deployed environment for the OAuth callback URL to resolve. The @modelcontextprotocol/sdk@1.29.0 dependency pulls in @hono/node-server as a transitive; check licensing before shipping.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?