fix(oauth): use getBaseUrl() in DCR so endpoints respect APP_URL behind a proxy - #317
Open
oliverhees wants to merge 136 commits into
Open
fix(oauth): use getBaseUrl() in DCR so endpoints respect APP_URL behind a proxy#317oliverhees wants to merge 136 commits into
oliverhees wants to merge 136 commits into
Conversation
Added instructions for building the development environment using Dev Containers in Visual Studio Code.
Updated the README
- Added mounted state to prevent hydration mismatches - Component now returns empty div during SSR - Only renders actual content after client-side mount - Fixes Next.js hydration error in sidebar layout
- Introduced `override_title` field in the namespace tool mappings schema to allow customization of tool titles. - Updated relevant repositories, serializers, and middleware to handle the new title override. - Enhanced frontend components to support displaying and editing tool titles, including search and filter functionalities. - Updated documentation to reflect the new tool override capabilities for titles.
Fixes spawned processes (npx, uvx, pip) failing with SSL errors in corporate proxy & firewall environments with custom CA signing certificates. Added to whitelist: - SSL/certificate vars: NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, etc. - Python-specific: REQUESTS_CA_BUNDLE, PIP_CERT, UV_CERT, etc. - Proxy vars: HTTP_PROXY, HTTPS_PROXY, NO_PROXY (both cases) Affects: process-managed-transport.ts, utils.ts
- add override_annotations column/migrations and propagate through repos, middleware, TRPC, and Zod schemas - expose tool namespace annotation editing in the enhanced tools table with validation, tooltips, reset behavior, and badge indicators - adjust tool details layout, README docs, and localization strings to explain annotations editing
- Replace extra headers from TOC - add `<!-- omit in toc -->`
Added instructions for building the development environment using Dev Containers in VSCode.
- Add ToolsSyncCache for efficient tracking of tool changes per MCP server. - Implement sync method in tools repository to upsert tools and delete obsolete ones. - Update tool management in frontend to utilize the new sync functionality. - Introduce vitest configuration and tests for tools synchronization logic. - Enhance tools repository with new methods for deleting obsolete tools and syncing tools.
Bumps [better-auth](https://github.com/better-auth/better-auth/tree/HEAD/packages/better-auth) from 1.2.9 to 1.4.2. - [Release notes](https://github.com/better-auth/better-auth/releases) - [Commits](https://github.com/better-auth/better-auth/commits/v1.4.2/packages/better-auth) --- updated-dependencies: - dependency-name: better-auth dependency-version: 1.4.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk) from 1.16.0 to 1.24.0. - [Release notes](https://github.com/modelcontextprotocol/typescript-sdk/releases) - [Commits](modelcontextprotocol/typescript-sdk@1.16.0...1.24.0) --- updated-dependencies: - dependency-name: "@modelcontextprotocol/sdk" dependency-version: 1.24.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
STDIO MCP servers launched via `uvx <pkg>@latest` cold-install their dependencies on first spawn. With no volume at the uv cache directory, every `docker compose up -d`/recreate wipes the writable layer and forces a cold install again — slow enough to trip the crash detector and stick the server in error_status=ERROR. - Dockerfile: create /home/nextjs/.cache/uv at build time, owned by nextjs (uid 1001) via the existing chown, so a fresh named volume inherits the correct ownership on first mount instead of root:root. - docker-compose.yml: mount a named volume (uv_cache) at the uv cache dir. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-leaks Fix subprocess leaks from race conditions in STDIO transport lifecycle
…oken feat: implement OAuth refresh_token grant type
…-timeout fix: touch session timestamps on access for idle-based cleanup Resolved conflicts in mcp-server-pool.ts by combining: - metatool-ai#260's per-server connection cap (canCreateConnectionForServer guard) - ai-dev's env-driven MAX_TOTAL_CONNECTIONS and generation-counter concurrency safety from metatool-ai#273 In createIdleSession(), the per-server cap check now runs before the generation guard, mirroring the already-merged createIdleSessionAsync(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-env-var fix: read SESSION_LIFETIME from env var for session cleanup
…mable-http-session-reinit-on-404 fix: re-initialize backend session on HTTP 404 Session not found Resolved conflict in metamcp-proxy.ts tools/call catch block: adopted metatool-ai#283's guarded re-initialization flow (only retry when isBackendSessionLostError(error) is true, otherwise rethrow), while keeping ai-dev's logger convention — converted metatool-ai#283's console.error/ console.warn calls in this block to logger.error/logger.warn to match. mcp-server-pool.ts (invalidateServerConnection) and the new session-error.ts / session-error.test.ts files merged cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(i18n): add Portuguese (pt) and Spanish (es) translations
docs(i18n): refine Chinese translation in README.md
Persist uv cache across container recreates
…tes-deployment docs: add Kubernetes deployment manifests
…v-configurable-session-lifetime feat(auth): make better-auth session lifetimes env-var configurable
…sert-race-and-ssr-safe fix(oauth): atomic upsert + SSR-safe OAuth provider (closes metatool-ai#296, metatool-ai#297)
…overy-detectors fix(session): hardened session-lost + transport-lost recovery detectors Resolved add/add conflict in session-error.ts / session-error.test.ts by adopting metatool-ai#293's hardened implementation wholesale. It is a superset of the naive isBackendSessionLostError() that metatool-ai#283 added: same 404 / "Session not found" / -32001 / -32600 matching, plus .cause-chain walking (depth 8), object/string throwable handling, and numeric/string .code inspection. Also adds isBackendTransportLostError() (-32603 "Not connected") and the combined isRecoverableBackendError(). These new detectors are additive and will be wired into the proxy retry paths by the stacked PR metatool-ai#310. The existing tools/call callsite in metamcp-proxy.ts keeps using isBackendSessionLostError() with identical (now hardened) behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r-recovery fix(proxy): invalidate-and-retry recovery in the aggregate list handlers Extends the session-recovery pattern (previously only on the tools/call handler from metatool-ai#283) to the four aggregate list handlers (tools/list, prompts/list, resources/list, resources/templates/list) via a new requestWithSessionRecovery() helper, using metatool-ai#293's isRecoverableBackendError (session-lost OR transport-lost). Conflict resolution: git auto-merged without markers but that silently produced a DUPLICATE invalidateServerConnection() (one from metatool-ai#283 already on ai-dev, one from metatool-ai#310). Kept metatool-ai#310's version — it cascades invalidation across every session slot for the serverUuid (not just the triggering session) and uses logger instead of console — and deleted metatool-ai#283's narrower duplicate. Signatures are identical so all callers are unaffected. The tools/call handler keeps its existing inline metatool-ai#283 recovery. session-error.ts/.test.ts were already current from the metatool-ai#293 merge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Self-heal server error_status on successful reconnect Removes the ERROR-state early-return in client.ts that made ERROR a one-way trapdoor (a recovered server could never reconnect, so could never clear the flag without a manual UI edit). Now it attempts the connection and, on success, calls serverErrorTracker.resetServerErrorState() to clear the flag; a genuinely-broken server gets re-flagged by the crash tracker. Conflict resolution: config.service.ts MCP_MAX_ATTEMPTS default — both ai-dev (via metatool-ai#260) and metatool-ai#311 already changed it from 1 to 3, so kept the agreed value 3 and retained metatool-ai#311's explanatory comment. server-error-tracker.ts fallbackMaxAttempts was likewise already 3 on ai-dev (metatool-ai#260); auto-merged with no change. Net new behavior is the client.ts self-heal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tool-ai#276 metatool-ai#276 hand-wrote drizzle/0014_oauth_refresh_token.sql but never ran `drizzle-kit generate`, so there was no meta/0014_snapshot.json and no _journal.json entry. Because docker-entrypoint.sh applies migrations via `drizzle-kit migrate` (journal-driven), the un-journaled 0014 was silently never applied — the refresh_token / refresh_token_expires_at columns would be missing at runtime and the OAuth refresh-token grant (metatool-ai#276) would fail. Regenerated 0014 from schema.ts so it now has a proper journal entry (idx 14) and snapshot. SQL is identical to the hand-written version (drizzle emits CREATE INDEX without IF NOT EXISTS). This also establishes a correct snapshot baseline so subsequent migrations can be regenerated cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ver-header-forwarding feat: per-server client header forwarding for SSE/StreamableHTTP Adds a forward_headers jsonb column on mcp_servers plus a header-forwarding module so configured client headers are forwarded to upstream MCP servers. Conflict resolution: - metamcp-proxy.ts import block: kept both metatool-ai#310's requestWithSessionRecovery import and metatool-ai#256's header-forwarding imports. - mcp-server-pool.ts cleanupSession: kept ai-dev's version (delete the sessionToServers mapping without recreating idle sessions — the metatool-ai#273 subprocess-leak fix). metatool-ai#256 had instead guarded the now-removed idle recreation with a forward_headers skip; ai-dev's removal already satisfies that intent, and getSession still skips idle reuse for forward_headers servers via serverRequiresForwardedHeaders(). Migrations: dropped metatool-ai#256's colliding 0014_dapper_jigsaw / 0015_small_wallow and regenerated 0015_forward_headers from the merged schema via drizzle-kit (sequential after the repaired 0014_oauth_refresh_token). Note: metatool-ai#256 also adds the `headers` field to the server zod schema, which fixes a batch of pre-existing `Property 'headers' does not exist` type errors once @repo/zod-types is rebuilt. Backend: 0 type errors, 85 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ools-on-endpoints feat: expose MetaMCP admin tools on endpoints Adds an enable_metamcp_admin_tools toggle on endpoints plus an admin-mcp module that surfaces MetaMCP admin operations as MCP tools (metamcp-admin__*), threaded through the proxy and both transport routers. Conflict resolution (combining metatool-ai#309's adminTools threading with metatool-ai#256's clientRequestHeaders threading, both of which add params to the same paths): - metamcp-server-pool.ts getServer(): kept both params (clientRequestHeaders, adminTools), kept metatool-ai#256's needsFreshServer header-forwarding gate, and added applyAdminToolsContext() on both the idle-reuse and new-server paths. - metamcp-proxy.ts / sse.ts / streamable-http.ts: kept both sides' imports and threaded both clientRequestHeaders and adminTools through getServer() calls. - Fixed a merge gap: the background idle-server creation in createIdleServerAsync() now sets internalSessionId (required by MetaMcpServerInstance, added by metatool-ai#309) — the metatool-ai#256-touched region had dropped it. - tools-registry getAdminToolsForMcp(): cast inputSchema to Tool["inputSchema"] (the zod-to-json-schema output is a valid object schema at runtime). Migrations: dropped metatool-ai#309's colliding 0014_metamcp_admin_tools and regenerated 0016_metamcp_admin_tools from the merged schema. zod-to-json-schema dependency (added by metatool-ai#309) installed. Backend: 0 new type errors, 85 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ublic-mcp-tool-calls feat: audit public MCP tool calls Adds an mcp_request_audit_logs table + audit middleware that records public endpoint tool calls (endpoint, auth, server, tool, status, duration), threading a requestContext (endpointName/auth) through the proxy and transport routers. Conflict resolution — this combined metatool-ai#288's requestContext threading with the already-merged metatool-ai#256 (clientRequestHeaders) and metatool-ai#309 (adminTools) threads that run through the same call chain. 15 conflicts across 7 files: - functional-middleware.ts MetaMCPHandlerContext: kept all three (clientRequest- Headers, endpointName, auth). - metamcp-server-pool.ts getServer()/createNewServer(): combined signatures to (clientRequestHeaders?, adminTools?, requestContext?). Rather than metatool-ai#288's separate "if (requestContext) build fresh + return" early branch, folded the audit requirement into metatool-ai#256's needsFreshServer gate (requestContext forces a fresh server) so an audited request still gets header forwarding AND adminTools. - metamcp-proxy.ts createServer()/handlerContext: kept clientRequestHeaders + endpointName + auth. - sse.ts / streamable-http.ts: threaded clientRequestHeaders, adminTools, and getRequestContext(authReq) through getServer() in signature order. - client.ts: kept metatool-ai#311's self-heal (no ERROR early-return) over metatool-ai#288's early-return+log variant. - mcp-server-pool.ts createIdleSession(): kept metatool-ai#273's generation-counter logic and grafted metatool-ai#288's metamcpLogStore.addLog on idle-session success. Migrations: dropped metatool-ai#288's colliding 0014/0015 and regenerated 0017_audit_public_tool_calls. Rebuilt @repo/trpc / @repo/zod-types (audit router + types). Backend: 0 new type errors, 89 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix rate limit duplicate counts, share postgres port in devcontainer, pnpm 10 upgrade + security overrides, and adopt MCP SDK 1.26 Lands metatool-ai#258's rate-limit duplicate-count fix, devcontainer postgres port sharing, the pnpm 9->10 upgrade, and the pnpm.overrides security pins (esbuild/tar/qs/lodash/prismjs/etc.). The overrides also force @modelcontextprotocol/sdk to >=1.26.0, which is a breaking bump (its schemas use the zod 4 API), so this commit also carries the full SDK 1.26 migration. SDK 1.26 / zod migration: - Resolution: do NOT pin zod globally. pnpm naturally dedupes the SDK to the workspace's zod 3.25.76 (the SDK accepts ^3.25 || ^4 and exposes zod 4's API via zod 3.25's zod/v4 subpath) while better-auth keeps its own zod 4 (it needs z.ipv4). A global zod override broke better-auth's frontend build. - Frontend: useConnection.ts exports a zod 3/4 compat contract (AnySchema/SchemaOutput/MakeRequestFn) mirroring Client.request; all inspector components + tool-management use MakeRequestFn instead of inline <T extends z.ZodType>; z.infer<typeof SdkSchema> -> SchemaOutput<...>; notificationTypes.ts composes via zod/v4 to match SDK schemas; Client is no longer over-parameterized (let it infer). - Backend: metamcp-proxy uses the SDK's exported List*Result types instead of z.infer<typeof *Schema>; client.ts drops the invalid server-side client capabilities (1.26 tightened the client capability type); tool-execution narrows the content-block union before reading .text; zod-to-mcp-schema casts through a narrowed signature to avoid TS2589. - Also fixed an metatool-ai#284<->metatool-ai#288 i18n merge gap: pt/es locales were missing the audit-logs namespace that metatool-ai#288 added to the Translations type (English fallback added). Verified: frontend `next build` succeeds; frontend tsc 0 errors (was 143 at branch start); backend builds, 89 tests pass, 0 new type errors. Runtime MCP behavior under SDK 1.26 (capabilities/content-block changes) was not verified against live MCP servers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ctions-env fix(mcp-server-pool): read MAX_TOTAL_CONNECTIONS from env in getInstance getInstance() was passing a hardcoded 100 as maxTotalConnections, which overrode the constructor's env-based default (added by metatool-ai#273). Since the constructor is private and only getInstance constructs the pool, the MAX_TOTAL_CONNECTIONS env var never actually took effect. Conflict resolution: combined metatool-ai#303's env-reading logic (with NaN / non-positive guarding) with ai-dev's getInstance signature — passing the resolved maxConn as maxTotalConnections while keeping the maxConnectionsPerServer argument (metatool-ai#260). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rver-side-token-exchange fix(oauth): server-side token exchange + pre-registered upstream OAuth clients Adds backend-side upstream OAuth token exchange (oauth-upstream/ module: token-exchange, refresh-on-401) so CORS-restricted enterprise providers (Salesforce/Okta/Auth0/...) work, plus a pre-registered upstream OAuth client UI (AdvancedOAuthSection) for providers without RFC 7591 dynamic registration. Conflict resolution (13 conflicts): - vitest.config.ts: kept ai-dev's @/ alias (already added by metatool-ai#310); metatool-ai#295's was a duplicate. - oauth.zod.ts: took metatool-ai#295's side for the 3 regions — its new exchange schemas and the wider UpstreamTokenResponseSchema for oauth_sessions.tokens. metatool-ai#300's oauth-sessions.repo is token-type-agnostic (spreads input.tokens with an && guard), so the widening is compatible. - mcp-servers.zod.ts, mcp-servers.json: union — kept metatool-ai#256's forward-headers schemas/strings and metatool-ai#295's pre-registered-OAuth schemas/strings. - edit-mcp-server.tsx, page.tsx: union — both the forward-headers field/parsing (metatool-ai#256) and the AdvancedOAuthSection + oauthClientInfo payload (metatool-ai#295) are wired into the form and apiPayload. No DB migration in metatool-ai#295. Verified: backend builds, 165 tests pass (incl. metatool-ai#295's new oauth-upstream + impl suites), 0 new type errors; frontend tsc 0, lint clean. Runtime OAuth flows not verified against a live upstream provider. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…st-auth-retry-csrf fix(oauth): post-auth retry + state CSRF validation (closes metatool-ai#298, metatool-ai#299) Stacked on metatool-ai#295. Adds: retry of the initial tools/list after token exchange to ride out the upstream session-establishment race (recoverFromPostAuthRace + attemptConnect refactor in client.ts), per-OAuth-session expected_state persistence, and server-side state validation at exchangeToken (CSRF defence). Conflict resolution: - client.ts: kept metatool-ai#301's attemptConnect / post-auth-race refactor, and grafted ai-dev's metatool-ai#311 self-heal (wasInErrorState + resetServerErrorState on success, no ERROR-state early-return) and metatool-ai#288 crash logging (metamcpLogStore) into it. Updated attemptConnect's doc comment to reflect the self-heal behavior. - oauth.zod.ts (UpsertOAuthSessionRequestSchema): kept ai-dev's non-nullable tokens/code_verifier (metatool-ai#300 atomic-upsert contract) and added metatool-ai#301's expected_state; merged both comment notes. Migration: dropped metatool-ai#301's colliding 0014_same_the_hunter and regenerated 0018_oauth_expected_state from the merged schema. Verified: backend builds, 186 tests pass (incl. metatool-ai#301's new suites), 0 new type errors; frontend tsc 0. Runtime OAuth/CSRF flows not verified against a live upstream provider. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
41 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The Dynamic Client Registration endpoint (
POST /oauth/register) builds theauthorization_endpoint/token_endpoint/userinfo_endpoint/revocation_endpointit returns fromreq.protocol + "://" + req.get("host").Behind a reverse proxy, the backend's
Hostis the internal address(e.g.
localhost:12009), so registered clients receive unreachablehttp://localhost:12009/oauth/*endpoints.oauth/metadata.tsalready solves this with thegetBaseUrl(req)helper from./utils, which prioritizesAPP_URLand thenX-Forwarded-*. As a result.well-known/oauth-authorization-serverreturns the correct public URLs whileDCR returns localhost — an inconsistency between the two handlers.
Spec-compliant clients (e.g. Claude) ignore the non-standard endpoint fields in
the DCR response and use the authorization-server metadata, so they work. Clients
that trust the DCR-returned endpoints get the localhost URLs and fail.
Repro
Fix
Use the existing
getBaseUrl(req)helper in both/oauth/registerhandlers, soregistration is consistent with
metadata.tsand respectsAPP_URL.