Skip to content

Connect a client ​

Every MCP client needs the same two things: the address of the endpoint, <PUBLIC_BASE_URL>/api/v1/mcp, and an API key sent in the Authorization: Bearer header. This page gives the configuration of the most common clients and a raw curl session to check an instance by hand.

Create the key ​

  1. Open Settings › API keys and click New key.
  2. Give it a name that says which client holds it, for example "Claude Desktop, laptop".
  3. Choose the scopes. For each domain, pick nothing, read only, or read and write:
    • Read-only exploration: workflows:read, executions:read, mailboxes:read, connections:read, tables:read. The assistant can describe, search, debug and propose, but writes nothing.
    • Building workflows: the same, with workflows:write instead of workflows:read. The assistant can also create drafts, update them and publish. Add tables:write only if the member is an administrator and wants the assistant to create tables.
  4. Copy the key when it is shown: it starts with mk_ and is displayed only once. Mankomail keeps only a hash of it.

A key never grants more than its member has; see Scopes and tools and API scopes for the grammar. Revoke the key from the same page when the client no longer needs it: the refusal is immediate.

Claude Code ​

Register the server with the http transport and the key as a header:

sh
claude mcp add --transport http <name> <PUBLIC_BASE_URL>/api/v1/mcp \
  --header "Authorization: Bearer mk_…"

Replace <name> with the name you want to see in Claude Code, for example your instance's name. The same server can be declared for a project in its .mcp.json:

json
{
  "mcpServers": {
    "<name>": {
      "type": "http",
      "url": "<PUBLIC_BASE_URL>/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer mk_…"
      }
    }
  }
}

Avoid committing a file that holds the key: prefer the command, which stores the server in your user configuration, or keep the project file out of version control.

Cursor ​

Cursor reads .cursor/mcp.json in the project (or ~/.cursor/mcp.json for every project). A remote server is declared with url and headers:

json
{
  "mcpServers": {
    "<name>": {
      "url": "<PUBLIC_BASE_URL>/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer ${env:MANKO_MCP_KEY}"
      }
    }
  }
}

Cursor documents ${env:NAME} interpolation in headers, so the key can live in the MANKO_MCP_KEY environment variable rather than in the file. A literal "Bearer mk_…" value works too.

Claude Desktop ​

Claude Desktop adds remote servers through Settings › Connectors › Add custom connector, a flow where the server drives authentication, typically with OAuth. Mankomail authenticates with a fixed header instead, so the verified route is the mcp-remote bridge: a small local program that Claude Desktop launches as a stdio server and that forwards every message to the remote endpoint with your header.

Edit claude_desktop_config.json (Settings › Developer › Edit Config) and add:

json
{
  "mcpServers": {
    "<name>": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "<PUBLIC_BASE_URL>/api/v1/mcp",
        "--header",
        "Authorization:${AUTH_HEADER}"
      ],
      "env": {
        "AUTH_HEADER": "Bearer mk_…"
      }
    }
  }
}

Two details come from the mcp-remote documentation: the header value is passed through an environment variable because some clients mangle spaces inside args, hence Authorization:${AUTH_HEADER} without a space; and mcp-remote tries Streamable HTTP first by default (--transport http-first), which is what Mankomail speaks. Restart Claude Desktop after saving; the tools appear under the server's name.

npx requires Node.js on the machine. If a client you use connects to remote Streamable HTTP servers with custom headers directly, prefer that over the bridge.

By hand, with curl ​

A raw session shows what a client does. Every message is a JSON-RPC request in a POST, with the key, Content-Type: application/json and an Accept header that lists both application/json and text/event-stream, as the transport requires.

1. Initialize. The server answers with its name, the instance's brand as title, its version and its capabilities:

sh
curl -s <PUBLIC_BASE_URL>/api/v1/mcp \
  -H "Authorization: Bearer mk_…" \
  -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",
      "capabilities": {},
      "clientInfo": { "name": "curl", "version": "0" }
    }
  }'

Because the transport is stateless, the response carries no Mcp-Session-Id, and the notifications/initialized message that clients normally send next is not required between two curl calls: nothing is kept on the server anyway.

2. List the tools. Send the negotiated version in MCP-Protocol-Version:

sh
curl -s <PUBLIC_BASE_URL>/api/v1/mcp \
  -H "Authorization: Bearer mk_…" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-06-18" \
  -d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }'

The result lists only the tools the key's scopes allow. Each one has a name (workflow_list), a title with the internal name (workflow.list), a description that ends with the required scope, an inputSchema, an outputSchema when the output is an object, and annotations (readOnlyHint, destructiveHint: false, openWorldHint: false).

3. Call a tool. Arguments follow the tool's inputSchema:

sh
curl -s <PUBLIC_BASE_URL>/api/v1/mcp \
  -H "Authorization: Bearer mk_…" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-06-18" \
  -d '{
    "jsonrpc": "2.0", "id": 3, "method": "tools/call",
    "params": { "name": "workflow_list", "arguments": { "includeArchived": false } }
  }'

A successful result carries the output twice: as JSON text in content[0].text and as an object in structuredContent. A refusal comes back as a result with isError: true and { "error": { "code", "message" } }, see Scopes and tools.

Without a valid key, the endpoint answers 401 with the API's error format before any MCP message is read. GET or DELETE on the endpoint answer 405.