feat: HTTP Stream transport with per-request credential injection#30
Open
fjprobos wants to merge 3 commits intoCognitionAI:mainfrom
Open
feat: HTTP Stream transport with per-request credential injection#30fjprobos wants to merge 3 commits intoCognitionAI:mainfrom
fjprobos wants to merge 3 commits intoCognitionAI:mainfrom
Conversation
Adds support for running the server in HTTP Stream mode (MCP_TRANSPORT=http), enabling a single deployed instance to serve multiple users with different Metabase credentials passed via request headers. - server.ts: detect MCP_TRANSPORT=http, configure FastMCP authenticate() to extract x-metabase-url, x-metabase-api-key (or username/password) from headers and attach a per-session MetabaseClient; falls back to stdio mode with shared client when env var is absent - All tool files: change signature from MetabaseClient instance to getClient(ctx) resolver so each execute() call uses the session's client - sse-server.js: add legacy SSE wrapper that spawns stdio processes (for clients that only support the older SSE transport) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- src/auth.ts: extract createAuthenticateHandler + createClientResolver so the logic can be imported and unit-tested independently of server startup - src/server.ts: use the new auth module (no behaviour change) - tests/auth.test.ts: 11 tests covering getClient resolver (session client, defaultClient, throws when neither) and authenticate handler (api-key, username/password, env fallback, 401 on missing URL, 401 on missing creds, session isolation) - tests/metabase-client.test.ts: constructor tests for api-key, username/ password, and invalid-credentials paths - tests/tool-filters.test.ts: parseToolFilterOptions for all flag combinations and default-to-essential behaviour - tests/config.test.ts: loadConfig and validateConfig with env var permutations - vitest.config.ts: Vitest config for ESM/TypeScript - package.json: add "test" and "test:watch" scripts - README.md: document HTTP Stream transport mode, Claude Code CLI integration, header reference table, and npm test command 31 tests, all passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… endpoint Use SSE_SERVER_URL env var (falls back to localhost:8010). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0dd7cdd to
ee52da5
Compare
fjprobos
pushed a commit
to madeofclay/metabase-mcp-server
that referenced
this pull request
Mar 13, 2026
Adds an OAuth 2.0 Authorization Code + PKCE gateway so that web-based MCP clients (Claude.ai, Claude Code web) can connect without pre-sharing credentials via request headers. ## Why this is needed The HTTP Stream transport (PR CognitionAI#30) requires clients to pass Metabase credentials as request headers. Web clients like Claude.ai do not support custom headers — they require a standard OAuth 2.0 flow. This gateway bridges that gap. ## How it works 1. Client discovers the gateway via `/.well-known/oauth-authorization-server` 2. User is redirected to `/oauth/authorize` — an HTML form asking for Metabase URL + API key (or username/password) 3. On submit, server stores credentials under a short-lived auth code and redirects back to the client 4. Client exchanges the code at `/oauth/token` for a signed JWT 5. All `/mcp` requests carry `Authorization: Bearer <JWT>` 6. Gateway validates the JWT, injects `x-metabase-*` headers, and proxies to the upstream HTTP Stream server ## Security - Credentials are never logged (only session prefix + status) - Client registration details logged only at LOG_LEVEL=debug - Auth codes are single-use with a 10-minute TTL - PKCE (S256) support for public clients - JWT signed with configurable JWT_SECRET ## Tests (28 passing) - OAuth discovery endpoints - Dynamic client registration (RFC 7591) - Authorization endpoint: form rendering, XSS escaping, validation - Token endpoint: api-key flow, username/password flow, single-use codes - PKCE: correct verifier, wrong verifier, missing verifier - MCP proxy: 401 without token, 401 with invalid token Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6 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.
Closes #29
Why this matters
The classic
stdiotransport requires one server process per user with credentials baked in at startup. This makes it incompatible with modern MCP clients like Claude Code CLI, which connect to HTTP endpoints and pass credentials per-request. This PR adds HTTP Stream transport support so a single deployed instance can serve many users — each with their own Metabase credentials.Changes
Core feature
src/auth.ts(new):createAuthenticateHandler()+createClientResolver()— the per-request credential injection logic extracted into a testable modulesrc/server.ts: detectsMCP_TRANSPORT=http, wires FastMCPauthenticate()hook to extractx-metabase-url,x-metabase-api-key(orx-metabase-username/x-metabase-password) from request headers; falls back to stdio mode with shared client when env var is absentMetabaseClientinstance to agetClient(ctx)resolver so eachexecute()call picks up the session's clientsse-server.js: legacy SSE wrapper for clients that only support the older SSE transportTests (31 passing, 0 pre-existing)
tests/auth.test.ts—createClientResolverandcreateAuthenticateHandlerunit teststests/metabase-client.test.ts— constructor with api-key, username/password, invalid-credentialstests/tool-filters.test.ts— flag parsing and default behaviourtests/config.test.ts—loadConfig/validateConfigenv var permutationsDocs
npm testcommandUsage — Claude Code CLI
Test plan
MCP_TRANSPORT=http node dist/server.jsstarts HTTP server on port 8011x-metabase-url+x-metabase-api-keyheaders — tools workMCP_TRANSPORT=http, server starts in stdio mode (no regression)npm test— 31 tests pass🤖 Generated with Claude Code