Convex MCP Gateway
Auth-aware MCP server for Convex: expose Convex functions as MCP tools with per-tool scopes, OAuth 2.1 discovery, optional DCR bridge, and audit log.
git clone https://github.com/tfohlmeister/convex-mcp-gateway.git
cd convex-mcp-gatewayThis upstream package has not been adapted or qualified for Bijection. Read the original documentation before adopting it.
Description
Auth-aware MCP server for Convex: expose Convex functions as MCP tools with per-tool scopes, OAuth 2.1 discovery, optional DCR bridge, and audit log.
Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.
Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.
Getting started
- Get the source at the revision above and read its package documentation.
- Check the required Bijection SDK version, dependencies, and external service credentials.
- Follow the package setup instructions and the guide to using components.
- Exercise the component in your deployment according to your project’s verification rules.
Open source
Maintained source: tfohlmeister/convex-mcp-gateway. License: See repository. Consult the repository for license terms, notices, and contributor attribution.
From the upstream publisher
Auth-aware Convex component that exposes selected Convex functions as MCP tools.
Explore the demo ↗Upstream overview
From the upstream package documentation.
An auth-aware MCP (Model Context Protocol) server component that exposes Convex functions as MCP tools for AI agents. Provides OAuth 2.1 protected-resource discovery, per-tool authorization with scopes/roles, comprehensive audit logging, and wire-error sanitization. Built as a Convex Component with type-safe tool registration and full integration with Convex's existing auth system.
Use cases
From the upstream package documentation.
-
Expose internal APIs to AI agents - Transform your existing Convex queries/mutations into MCP tools that Claude, GPT, or custom agents can discover and call with proper authentication
-
Build role-based agent workflows - Create different tool catalogs for different user roles, where agents only see and can invoke functions their authenticated user has permission to access
-
Audit AI agent interactions - Track every tool call with detailed logging including argument redaction, identity context, and error details for compliance and debugging
-
Bridge existing auth systems with MCP - Use your current Clerk/Auth0/custom JWT setup without changes while providing OAuth 2.1 discovery for MCP clients
-
Prototype agent integrations locally - Test full MCP workflows in development with the included convex-test helper for end-to-end testing
How it works
From the upstream package documentation.
You register Convex functions as MCP tools using defineMcpQuery, defineMcpMutation, or defineMcpAction with type-safe argument validation and optional metadata for authorization. The component handles the complete MCP 2025-06-18 Streamable HTTP protocol including session management, content negotiation, and identity-bound cleanup.
A single authorize callback controls both tool visibility (tools/list) and execution (tools/call) using your existing ctx.auth.getUserIdentity() flow. The gateway automatically filters the tool catalog based on what each authenticated user can actually invoke, ensuring agents only see relevant capabilities.
The component mounts as a standard Convex httpAction in your router and provides OAuth 2.1 protected-resource discovery with RFC 9728 metadata endpoints and proper WWW-Authenticate headers. Every tool invocation gets logged to an audit table with configurable argument redaction, while wire errors are sanitized to prevent information leakage.
Upstream package FAQ
Does convex-mcp-gateway work with existing Convex auth providers?
Yes, convex-mcp-gateway integrates with any OAuth 2.1/OIDC provider that Convex supports including Clerk, Auth0, Pocket-ID, AWS Cognito, and custom JWT issuers. It uses your existing ctx.auth.getUserIdentity() without requiring code changes to your auth setup.
How do I control which tools are visible to different users?
Configure a single authorize callback that receives toolMetadata and decides access per call. The same callback filters tools/list (what users see) and tools/call (what they can execute) using a mode parameter. You can implement role-based or scope-based access control in this callback.
What MCP protocol features does the gateway support?
Convex MCP Gateway implements MCP 2025-06-18 Streamable HTTP with sessions, Accept negotiation, MCP-Protocol-Version validation, and identity-bound DELETE operations. It handles the full JSON-RPC envelope and session lifecycle automatically.
Can I use this with browser-based MCP clients like claude.ai?
Yes, convex-mcp-gateway includes an optional OAuth bridge mode that provides RFC 7591 Dynamic Client Registration for browser MCP clients when your IdP doesn't support DCR natively. This enables integration with claude.ai and other browser-based MCP clients.
Use with agents and CLI
Give your coding agent the component’s source identity and setup guidance.
Help me integrate Convex MCP Gateway with Bijection.
Read: https://bijection.com/components/convex-mcp-gateway/SKILL.md
Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.Upstream README.md
View repository ↗Documentation from tfohlmeister/convex-mcp-gateway, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.
convex-mcp-gateway
Auth-aware MCP server for Convex. Expose selected Convex functions as MCP tools, bring your own JWT issuer, declare scopes/roles per tool, get an audit log and OAuth 2.1 protected-resource discovery for free.
Built as a Convex Component.
Features
- Type-safe tool registration —
defineMcpQuery/defineMcpMutation/defineMcpActiondeclare a Convex function as an MCP tool with end-to- end-typedargsand (optional)returnsvalidators - MCP 2025-06-18 Streamable HTTP — sessions,
Acceptnegotiation,MCP-Protocol-Versionvalidation, identity-boundDELETE, single- frame SSE - One authorize callback — gates
tools/calland filterstools/listwithmode: "list" | "call"; uses your existingctx.auth.getUserIdentity() - OAuth 2.1 protected-resource discovery — RFC 9728 metadata,
RFC 6750
WWW-Authenticateheaders, multi-tenant ready - Optional OAuth bridge — RFC 8414 AS metadata wrap + RFC 7591 DCR for browser MCP clients (claude.ai) against IdPs without DCR support
- Audit log — one row per call with per-tool argument redaction (verbatim / dropped / dotted-path redacted)
- Wire-error sanitization — generic message on the wire, full detail
in audit;
ConvexErrorpasses through for deliberate user-facing messages convex-testhelper —convex-mcp-gateway/testexports a one-lineregister(t)that hooks the component into aconvexTestinstance so your host tests can exercise the full/mcp/round-trip in-process. See Testing
What it does
You mount the gateway in a single httpAction and pass an authorize
JS callback that decides per call whether the request goes through.
The gateway handles the JSON-RPC envelope, the Streamable-HTTP session
lifecycle, the OAuth discovery doc, the WWW-Authenticate headers, and
the audit log. Your existing Convex auth (Clerk, Auth0, Pocket-ID,
custom JWT issuer) just works.
A standalone editorial-styled version of the diagram is at
docs/diagrams/architecture.html;
in-depth sequence and data-flow diagrams live in
docs/architecture.md.
Try it
The companion repo convex-mcp-gateway-demo is a notes app that registers five tools (public, identity-gated, role-gated) and shows the audit log live. Three run modes including a local-backend setup that needs no Convex account:
git clone https://github.com/tfohlmeister/convex-mcp-gateway-demo
cd convex-mcp-gateway-demo
pnpm install && pnpm local:start # in one terminal
pnpm convex:dev && pnpm convex:run mcp:registerDefaults && pnpm dev
Quickstart
pnpm add convex-mcp-gateway
// convex/convex.config.ts
import { defineApp } from "convex/server";
import mcpGateway from "convex-mcp-gateway/convex.config";
const app = defineApp();
app.use(mcpGateway);
export default app;
// convex/mcp.ts — register tools
import { v } from "convex/values";
import { McpGateway, defineMcpQuery } from "convex-mcp-gateway";
import { api, components } from "./_generated/api.js";
import { internalMutation } from "./_generated/server.js";
const gateway = new McpGateway(components.mcpGateway);
export const registerDefaults = internalMutation({
args: {},
handler: async (ctx) => {
await gateway.register(
ctx,
[
defineMcpQuery({
name: "invoices_summary",
description: "Public invoice counter.",
fn: api.invoices.summary,
args: {},
metadata: { public: true },
}),
defineMcpQuery({
name: "invoices_list",
description: "List invoices for the authenticated user.",
fn: api.invoices.list,
args: { status: v.optional(v.string()) },
}),
],
);
},
});
// convex/http.ts — mount the gateway with your authorize callback
import { httpRouter } from "convex/server";
import {
McpGateway,
type McpAuthorizerHandler,
} from "convex-mcp-gateway";
import { components } from "./_generated/api.js";
import { httpAction } from "./_generated/server.js";
const gateway = new McpGateway(components.mcpGateway);
const authorize: McpAuthorizerHandler = async (ctx, { toolMetadata }) => {
const meta = (toolMetadata ?? {}) as { public?: boolean };
if (meta.public) return { allowed: true };
const identity = await ctx.auth.getUserIdentity();
if (!identity) return { allowed: false, reason: "Unauthorized" };
return { allowed: true };
};
const http = httpRouter();
const mcp = httpAction(async (ctx, req) =>
gateway.handleMcpRequest(ctx, req, { authorize }),
);
// Mount both /mcp/ and /mcp — some clients (claude.ai) strip the
// trailing slash from the configured URL before POSTing.
for (const path of ["/mcp/", "/mcp"]) {
http.route({ path, method: "POST", handler: mcp });
http.route({ path, method: "GET", handler: mcp });
http.route({ path, method: "DELETE", handler: mcp });
}
export default http;
npx convex dev --once
npx convex run mcp:registerDefaults
# Talk to it (Streamable HTTP — initialize first, then send commands).
SESSION=$(curl -sSD - -X POST "$CONVEX_SITE_URL/mcp/" \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}' \
| awk '/^[Mm]cp-[Ss]ession-[Ii]d:/ {print $2}' | tr -d '\r')
curl -X POST "$CONVEX_SITE_URL/mcp/" \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H "mcp-session-id: $SESSION" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
The anonymous client sees only invoices_summary (the public: true
tool); calling invoices_list without a Bearer returns HTTP 401 with a
WWW-Authenticate header pointing at your discovery endpoint. Any
spec-compliant MCP client (the official MCP Inspector, IDE plugins,
agent runtimes) handles the session and OAuth handshakes
automatically. See Getting Started for
the full walkthrough.
Documentation
- Getting Started — install, register, call, in five minutes
- Architecture — component model, data flow, sequence diagrams
- Authorization — authorizer contract,
mode: "list"vs"call", scope/role recipes - OAuth 2.1 setup — RFC 9728 discovery, host-side mount, multi-tenant
- OAuth bridge mode — opt-in DCR + AS metadata wrap + userinfo token validation, for browser MCP clients (claude.ai) against IdPs that don't support Dynamic Client Registration (Pocket-ID, etc.)
- Audit log — reading, filtering, redacting, pruning
- Testing — convex-test patterns, identity injection, swappable authorizers
Design choices, briefly
- One authorizer query, deny-by-default. The component has no
notion of scopes, roles, JWT claims, or tenants. It calls one
internalQueryper dispatch and lets it decide. Until that query is registered, nothing works. - Scope-aware
tools/list. The catalog visible to a caller equals the set of tools they could actually invoke; the same authorizer filters both with amode: "list"discriminator. - Audit never alters dispatch. Every audit write is best-effort;
failures log and swallow. A successful tool mutation always returns
ok: true. - Identity propagation is free. Convex validates inbound Bearer
tokens against your
auth.config.tsbefore any function runs; the same identity flows into the authorizer and the tool handler. - OAuth discovery is opt-in. Configure an authorization server, and
401s carry
WWW-Authenticateheaders per RFC 6750 and the RFC 9728 path-prefix metadata URL.
Local development
pnpm install
pnpm check # codegen + typecheck + tests + lint
# Iterate against a local Convex backend:
pnpm local:start # downloads the pinned binary, writes .env.local
# runs on :3310 / :3311 (no Docker)
The local backend uses upstream test fixture credentials checked into
get-convex/convex-backend — public, deterministic, safe to commit.
See docs/testing.md for convex-test patterns and
CONTRIBUTING.md for the contribution workflow.
Roadmap
Auth model: bring your own IdP. Any OAuth 2.1 / OIDC issuer that
Convex's auth.config.ts can validate against (Pocket-ID, Auth0,
Clerk, Keycloak, AWS Cognito, Authentik, custom JWT issuer) plugs in
without code changes. We deliberately do not ship a bundled
authorization server: building DCR + PKCE + token issuance + key
rotation duplicates what every IdP already does, and an MCP gateway
that's also an IdP would be two security-critical surfaces in one
component. The gateway only implements the resource-server side of
MCP's OAuth profile (RFC 9728 discovery, RFC 6750 WWW-Authenticate).
A separate bridge mode (RFC 8414 metadata + RFC 7591 DCR wrap) lets
hosts whose upstream IdP doesn't speak DCR still serve browser MCP
clients — see docs/oauth-bridge.md.
Future
- Capability tokens for agent-spawning workflows (small JWT helper,
not a full AS:
gateway.signCapabilityToken({ tools, runId, ttl })plus authorizer-side validation) mcpResourceandmcpPromptMCP primitives- Multi-tenant pre-baked patterns (per-tenant URL, RFC 8707 audience binding)
- Audit-log UI
- Reverse direction (consume external MCP servers as Convex tools)
- Smithery.ai listing
- RFC 8693 token exchange for cross-domain identity
See the repo's GitHub issues for finer-grained tracking.
Security
If you discover a vulnerability, please follow SECURITY.md (do not file a public issue).