# The ClosedMCP library What you use depends on where your MCP server runs. | Your MCP runs... | Use | |---|---| | On ClosedMCP (a [Serverless MCP](/docs/serverless)) | the **hosted contract** below: four headers in, two `_meta` keys out. Nothing to install. | | On your own servers | the **Log in with ClosedMCP SDK** (`@closedmcp/sdk` for TypeScript, `closedmcp` for Python, also Go and Rust). See [SDK reference](/docs/sdk). | Do not wrap a hosted app with the SDK's server wrapper (`serveWithClosedMcp` / `serve_with_closedmcp`). It expects a signed pass in `Authorization: Bearer`, and ClosedMCP removes that header before a call reaches a hosted app, so every call would be rejected. The SDKs' hosted module (`closedmcp.hosted` in Python, `@closedmcp/sdk/hosted` in TypeScript, both in the base image) reads the caller, builds the `_meta` keys below and, from SDK 0.3.0, serves the app: | Python | TypeScript | Does | |---|---|---| | `serve(mcp)` | `serve(() => server)` | serves MCP at `/mcp` on `0.0.0.0:8080`, stateless (0.3.0+) | | `caller()`, `try_caller()` | `caller(extra)`, `tryCaller(extra)` | the person calling, from the headers below | | `open_channel(...)`, `events([...])` | `openChannel({...})`, `events([...])` | the `_meta` keys below, checked against the limits | | `database_url()` | `databaseUrl()` | `DATABASE_URL`, or an error if no database is enabled | `closedmcp_create_project` starts every project with this already wired up; give your AI app the [closedmcp-builder skill](https://closedmcp.com/skills/closedmcp-builder.zip) ([how to install](/docs/serverless#build-with-your-ai-app)) too. ## What is already installed Every hosted app and every `closedmcp_run` sandbox starts from the same base image: - Node 22 with `@modelcontextprotocol/sdk`, `fastmcp`, `@closedmcp/sdk`, `pg` and `postgres`, found through `NODE_PATH` (so load them with `require`). - Python 3.12 with `mcp`, `fastmcp` 2, `psycopg[binary]`, `SQLAlchemy`, `uvicorn` and `closedmcp`. - `psql`, `bash`, `git`, `curl` and `jq`. Your own `node_modules` or Python packages win over these. ## In: who is calling | Header | Meaning | |---|---| | `X-ClosedMCP-User` | the person's ID, stable | | `X-ClosedMCP-Email` | their email | | `X-ClosedMCP-Org` | their company's org ID, the slug (e.g. `dacme_io`) | | `X-ClosedMCP-Client` | the AI app they use; display only | ClosedMCP sets these on every call and removes copies sent by anyone else. - Python (FastMCP 2): `get_http_headers(include_all=True).get("x-closedmcp-email")`, with `from fastmcp.server.dependencies import get_http_headers`. - Node (official MCP SDK): `extra.requestInfo?.headers["x-closedmcp-email"]` in the tool handler. ## Out: event channels | `_meta` key in a tool result | Effect | |---|---| | `"closedmcp/channel": {name, topic, ttl}` | opens a channel the caller can wait on | | `"closedmcp/events": [{topic, data}, ...]` | delivers each `data` to every open channel of this app with that `topic` | - Python (FastMCP 2): return `ToolResult(content="...", meta={...})`, with `from fastmcp.tools.tool import ToolResult`. - Node (official MCP SDK): return `{ content: [...], _meta: {...} }`. Details and limits: [Event channels](/docs/event-channels).