Architecture & Transport
How the Metadot MCP server is built and the transports it supports
Metadot exposes MCP as a hosted HTTP endpoint at https://metadot.eu/api/mcp. Your AI tool connects over the network, signs in via OAuth 2.1, and calls tools through JSON-RPC over Streamable HTTP. Underneath, each tool is a thin wrapper over Metadot's existing service layer — no new business logic.
Hosted Architecture
AI Tool (Claude Desktop, ChatGPT, Claude.ai, Cursor, Claude Code, Windsurf, Antigravity)
│
│ MCP Protocol over Streamable HTTP
│ Authorization: Bearer <oauth_access_token>
▼
┌────────────────────────────────────────────────────┐
│ https://metadot.eu/api/mcp (Next.js route) │
│ │
│ • validateMcpAccessToken — verifies OAuth token │
│ • buildAuthContext — loads user identity │
│ • getEnabledAppsForUser — resolves app scope │
│ • WebStandardStreamableHTTPServerTransport │
└────────────────────┬───────────────────────────────┘
│ Direct service calls
▼
@metadot/mcp-server (tool registry)
│
▼
@metadot/crm, @metadot/tickets,
@metadot/projects, ... (13+ packages)
│
▼
PostgreSQL (Drizzle ORM)
OAuth 2.1 Flow
Authentication is handled by three Next.js route handlers alongside the MCP endpoint:
/.well-known/oauth-authorization-server— discovery metadata (RFC 8414)/api/mcp/oauth/register— dynamic client registration (RFC 7591)/api/mcp/oauth/authorize— browser consent screen/api/mcp/oauth/token— authorization code + refresh token exchange
MCP clients discover endpoints, register themselves, redirect the user to the consent page, and exchange the authorization code for a 24-hour bearer token plus refresh token. PKCE (S256) is mandatory.
Design Principles
- Tool factory pattern —
defineReadToolanddefineWriteToolhandle workspace resolution, RBAC, event emission, and serialization. Each tool definition is about 10 lines of code. - No new business logic — every tool wraps an existing service function. The service layer is the single source of truth.
- App-based filtering — tools are registered conditionally per-request based on
getEnabledAppIds()for each of the user's workspaces. Disabled apps never appear in the tool list. - Workspace cache —
resolveWorkspaceresults are cached in memory with a 5-minute TTL to avoid repeated DB queries while still catching role changes. - Event bus — write tools emit events through the same bus used by the platform, triggering workflows, search index updates, and notifications.
- Session management — the hosted endpoint keeps per-session transports with a 30-minute TTL, evicting stale sessions on each POST.
Self-Hosting & Local Development
This section only applies if you are running the Metadot codebase yourself (self-hosted deploy, or contributing to the project locally). SaaS users should ignore it.
The @metadot/mcp-server package can also run as a standalone stdio or HTTP server, directly connected to your own Postgres database. This is what Metadot uses internally for contributor tooling and what self-hosters run behind their own auth.
Stdio (contributor default)
The AI tool spawns the server as a subprocess:
METADOT_API_KEY=mk_... DATABASE_URL=postgresql://... npx tsx packages/mcp-server/src/index.ts
HTTP/SSE (shared local access)
Runs as a standalone HTTP server:
MCP_TRANSPORT=http MCP_PORT=3100 \
METADOT_API_KEY=mk_... DATABASE_URL=postgresql://... \
npx tsx packages/mcp-server/src/index.ts
Connect your MCP client to http://localhost:3100/mcp. HTTP mode requires Authorization: Bearer <token> on every request (where <token> is a PAT, not an OAuth token).
Health check: GET http://localhost:3100/health returns {"status":"ok","version":"0.1.0"}
When to Use Self-Hosted Modes
- You are contributing to the Metadot codebase and want to iterate on tool definitions locally
- You are self-hosting the Metadot platform behind your own auth
- You are running the MCP server on air-gapped infrastructure with direct DB access
For everything else, the hosted endpoint at https://metadot.eu/api/mcp is the supported path.
Was this page helpful?