plym Docs
    plym.io

    Client setup

    Add your plym MCP endpoint to Claude Code, Cursor, VS Code, or Claude Desktop. The two credential headers, a full config for each client, and the curl pair that proves the endpoint works before you blame the client.

    Updated Aug 04, 2026

    On this page

    Connect an AI client to your blog so it can draft posts and upload images without the admin UI. This page assumes the MCP server is already on — see Introduction if plym enable mcp has not been run.

    Every client needs the same three things: the endpoint URL, a plym account email in X-User-Identity, and that account's password in X-Mcp-Token. Clients that can send custom HTTP headers connect directly. Clients that cannot need the mcp-remote bridge, covered below.

    Before you start

    Create a dedicated account for the client in Admin > Users and give it the editor role. An editor can create posts and upload media; it cannot change users, roles, or instance config, which an administrator account handed to a client could.

    Add the server to Claude Code

    1. Add the endpoint with both headers:
    claude mcp add --transport http plym https://blog.example.com/mcp \
      --header "X-User-Identity: ada@example.com" \
      --header "X-Mcp-Token: YOUR_PLYM_PASSWORD"
    1. Confirm what was stored and whether the connection works:
    claude mcp get plym
    plym:
      Scope: Local config (private to you in this project)
      Status: ✓ Connected
      Type: http
      URL: https://blog.example.com/mcp
      Headers:
        X-User-Identity: ada@example.com
        X-Mcp-Token: YOUR_PLYM_PASSWORD
    1. Ask the client to list your posts. Six tools named create_post, upload_media, list_posts, list_users, get_from_url, and md_from_html are now available.

    Use the command above rather than a checked-in file. The equivalent .mcp.json works and is shared with everyone who clones the repository, password included:

    {
      "mcpServers": {
        "plym": {
          "type": "http",
          "url": "https://blog.example.com/mcp",
          "headers": {
            "X-User-Identity": "ada@example.com",
            "X-Mcp-Token": "YOUR_PLYM_PASSWORD"
          }
        }
      }
    }

    Cursor

    Write ~/.cursor/mcp.json for every project, or .cursor/mcp.json for one. Cursor expands ${env:NAME} in header values, so export PLYM_MCP_TOKEN in your shell profile and keep the password out of the file:

    {
      "mcpServers": {
        "plym": {
          "url": "https://blog.example.com/mcp",
          "headers": {
            "X-User-Identity": "ada@example.com",
            "X-Mcp-Token": "${env:PLYM_MCP_TOKEN}"
          }
        }
      }
    }

    VS Code

    Write .vscode/mcp.json. A promptString input with password: true makes VS Code ask for the password once and store it in the credential manager:

    {
      "inputs": [
        {
          "type": "promptString",
          "id": "plym-password",
          "description": "plym account password",
          "password": true
        }
      ],
      "servers": {
        "plym": {
          "type": "http",
          "url": "https://blog.example.com/mcp",
          "headers": {
            "X-User-Identity": "ada@example.com",
            "X-Mcp-Token": "${input:plym-password}"
          }
        }
      }
    }

    Claude Desktop and other stdio-only clients

    Claude Desktop connects to remote servers by OAuth and has no field for a custom header, so it cannot authenticate against plym directly. Bridge it with mcp-remote, which speaks stdio to the client and HTTP to your endpoint:

    {
      "mcpServers": {
        "plym": {
          "command": "npx",
          "args": [
            "-y",
            "mcp-remote",
            "https://blog.example.com/mcp",
            "--header",
            "X-User-Identity:${PLYM_EMAIL}",
            "--header",
            "X-Mcp-Token:${PLYM_PASSWORD}"
          ],
          "env": {
            "PLYM_EMAIL": "ada@example.com",
            "PLYM_PASSWORD": "YOUR_PLYM_PASSWORD"
          }
        }
      }
    }

    Two rules for that config. ${NAME} substitutes a header value only, so keep the header name literal in the argument — "--header", "${WHOLE_HEADER}" reaches the server as nothing and every tool call returns Missing credentials. Add --allow-http to the argument list when the endpoint is http://, which it is when you point a client at http://localhost:9173/mcp for a local test.

    Do not run python -m plym.mcp as a stdio server for these clients. It starts and lists tools, but stdio carries no HTTP headers, so no tool call can authenticate.

    Test the endpoint without a client

    The first call proves the endpoint is reachable and returns a session id. It does not check credentials, because initialize accepts requests without them:

    curl -sD - -o /dev/null -X POST https://blog.example.com/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","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
    HTTP/1.1 200 OK
    mcp-session-id: 49887e27b2664b7f9d3a1c05e7b8a2f1
    content-type: text/event-stream

    This pair checks the credentials, by carrying that session id into a real tool call:

    SESSION=$(curl -sD - -o /dev/null -X POST https://blog.example.com/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","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' \
      | grep -i '^mcp-session-id:' | tr -d '\r' | cut -d' ' -f2)
    
    curl -s -X POST https://blog.example.com/mcp \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json, text/event-stream' \
      -H "mcp-session-id: $SESSION" \
      -H 'X-User-Identity: ada@example.com' \
      -H 'X-Mcp-Token: YOUR_PLYM_PASSWORD' \
      -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_posts","arguments":{}}}'
    event: message
    data: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"[{\"id\":1,\"slug\":\"hello-world\",...}]"}],"isError":false}}

    An isError of true with a message inside content means the endpoint is fine and the credentials or the role are not.

    Frequently asked questions

    The client lists all six tools but every call fails with `Missing credentials`
    The client connected without sending your headers. `initialize` and `tools/list` need no credentials, so a tool list proves nothing about authentication. Check the header names character for character: `X-User-Identity` and `X-Mcp-Token`.
    Calls return "Authentication failed: check your email and password."
    The email and password pair was rejected by plym. `X-Mcp-Token` takes the account password, not an API key, and there is no separate MCP credential to generate.
    `create_post` and `upload_media` fail while `list_users` works.
    The account has the reader role. Only editor or higher roles can create posts or upload media.