MCP integration

Client setup

The exact configuration for each client. Which credential you need comes down to one question — can your client identify itself with a hosted metadata document (CIMD)? If it can, the endpoint URL is all you need. If it can't, use a long-lived key.

Which credential does my client need?

Work down this list and stop at the first line that matches. The deciding factor is a client capability, not a brand — any client that speaks CIMD gets the URL-only path, whether or not it is listed below.

  1. It supports CIMD → paste the endpoint URL and you are done (Cursor needs one extra line — we host its document, see below). The browser opens once for sign-in, workspace and consent; nothing to register, no key to store.
  2. It speaks DCR (registers itself on first use) → also just the endpoint URL. n8n and Dify work this way; leave their «Use Dynamic Client Registration» switch on.
  3. Neither, but you can set a request header → issue a popkit_it_… key in Settings and put it in Authorization. This is also the right answer for anything unattended.
  4. It speaks OAuth and lets you type a client_id → host a small metadata document yourself and use its URL as the client_id. That turns any OAuth-capable client into a CIMD client — see below.
  5. None of the above → it cannot connect over OAuth. In practice this is Continue, whose MCP config has no OAuth fields at all.
ClientCredentialWhy
Claude Code · claude.aiOAuth (nothing to register)Speaks CIMD — identifies itself with a hosted metadata URL
CursorOAuth (nothing to register)We host its client metadata document — paste one URL, no key. See below
Cline · Continuepopkit_it_… in a headerCline can take a hand-filled client_id but it is undocumented; Continue has no OAuth fields at all
n8n · DifyOAuth (registers itself)Their OAuth uses DCR, which production now offers — keep the DCR switch on, do not hand-fill
Your own scriptpopkit_it_… in a headerSimplest option — though hosting your own metadata document gets you OAuth instead
Anything elseRun the check belowThe question is whether it speaks CIMD, not which product it is

Most clients share one JSON shape — a mcpServers map with url + headers:

The common shape
{
  "mcpServers": {
    "popkit": {
      "url": "https://popkit.loxily.com/api/mcp",
      "headers": {
        "Authorization": "Bearer popkit_it_YOUR_TOKEN"
      }
    }
  }
}

CIMD clients (Claude Code · claude.ai)

Claude Code
claude mcp add --transport http popkit https://popkit.loxily.com/api/mcp

The authorization page opens on first use: sign in → pick a workspace → approve the scopes. No token to paste, nothing to store. For claude.ai, add the same URL as a custom connector.

The consent screen is where scopes are granted, so what you approve there is the ceiling for that connection — never more than your own role allows.

Cursor

Cursor does not self-register, but it lets you type a client_id — and we host one for you, so there is no document to publish and no key to store. Put this in ~/.cursor/mcp.json:

~/.cursor/mcp.json
{
  "mcpServers": {
    "popkit": {
      "url": "https://popkit.loxily.com/api/mcp",
      "auth": {
        "CLIENT_ID": "https://popkit.loxily.com/.well-known/mcp-client/cursor"
      }
    }
  }
}

That CLIENT_ID is a URL we serve: it is a client metadata document naming Cursor and its two fixed callbacks. Press Connect and the browser opens once — sign in, pick a workspace, approve. Leave CLIENT_SECRET out entirely; this is a public client and PKCE does the rest.

Hosting that document for you is a convenience, not an endorsement — the consent screen will still say popkit has not verified this client. If you would rather control it yourself, host your own document (see «Any other MCP client») and use its URL instead.

Prefer a key? A popkit_it_… in a header works too — see the common shape above.

Cline · Continue

Cursor — put it in ~/.cursor/mcp.json (global) or .cursor/mcp.json in the project root (shared with the team; the project file wins on a name clash). Cursor interpolates ${env:NAME} inside url and headers, so the token need not sit in the file:

~/.cursor/mcp.json
{
  "mcpServers": {
    "popkit": {
      "url": "https://popkit.loxily.com/api/mcp",
      "headers": {
        "Authorization": "Bearer ${env:POPKIT_MCP_TOKEN}"
      }
    }
  }
}

Check it under Cursor → Settings → MCP; the server should list the popkit tools. Cursor does support OAuth, but only through dynamic registration — against production that path fails, so use the header above.

Cline — open the settings file from the command palette (Cline: Open MCP Settings) and merge the common shape into it. The file really lives at ~/.cline/data/settings/cline_mcp_settings.json (Cline's own docs list an older path that no longer applies). It reloads on save; no restart.

Continue — YAML, not JSON, and its OAuth support only works for sse servers, so a header is the only option here. Put this in ~/.continue/config.yaml, or as a block file under .continue/mcpServers/:

~/.continue/config.yaml
name: popkit-mcp
version: 0.0.1
schema: v1
mcpServers:
  - name: popkit
    type: streamable-http
    url: https://popkit.loxily.com/api/mcp
    requestOptions:
      headers:
        Authorization: Bearer ${{ secrets.POPKIT_MCP_TOKEN }}
Continue's extensions cannot read variables exported in your shell — put POPKIT_MCP_TOKEN=… in ~/.continue/.env instead. MCP tools are only available in agent mode.

n8n · Dify

n8n — add an MCP Client Tool node to your agent (or the plain MCP Client node for a single call) and fill in:

  • Server Transport: HTTP Streamable (the default on recent node versions; the SSE option is deprecated)
  • Endpoint: https://popkit.loxily.com/api/mcp
  • Authentication: MCP OAuth2 API → create the credential, leave Use Dynamic Client Registration on, fill only Server URL with the endpoint, save, then press Connect. The browser opens once: sign in, pick a workspace, approve. Nothing to paste.
  • Prefer a key instead (unattended runs, no browser)? Pick Bearer Auth and paste a popkit_it_… into the single Bearer Token field.
Needs n8n ≥ 1.104 for HTTP Streamable; ≥ 2.33 is recommended — earlier versions leaked MCP sessions after a run and hammered the endpoint with reconnects until the idle timeout. You may see n8n issue #24967 quoted as a transport bug: it was diagnosed as the session leak, not the transport, and is fixed.

Dify — Integrations → Tools → MCP → Add MCP Server (HTTP). Fill Server URL with the endpoint, give it a name and a server identifier, then add a header under Headers: name Authorization, value Bearer popkit_it_…. Or turn the OAuth authorization on and leave Use Dynamic Client Registration enabled — that now works, and saves you managing a key. Press Get tools to import.

Any other MCP client

Nothing above is a whitelist — popkit never inspects which product is calling. If your client isn't listed, find out which of the four paths it lands on:

  1. Point it at the endpoint and watch what it does. A client that speaks CIMD sends the URL of its own metadata document as the client_id; the browser then opens and you are finished. Many clients say "OAuth supported" but mean DCR — the tell is an error mentioning registration.
  2. Check its docs for client_id. If it lets you type one in, you are on path 3 below, whatever it says about registration.
  3. Otherwise look for a custom-header setting. Anything that can send Authorization: Bearer … works with a long-lived key.

Path 3 — host your own metadata document. CIMD means the client_id *is* a URL, and popkit fetches that URL to learn who the client is. So publish this as JSON at any public HTTPS address and hand your client that address as its client_id:

https://example.com/mcp-client.json
{
  "client_id": "https://example.com/mcp-client.json",
  "client_name": "My Agent",
  "redirect_uris": ["http://127.0.0.1:8765/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"]
}

Requirements are short: served over HTTPS as JSON, client_name non-empty, redirect_uris a non-empty array of absolute URIs, and token_endpoint_auth_method either none (the default) or private_key_jwt. Loopback callbacks such as http://127.0.0.1:PORT/callback are accepted, so this works for command-line and desktop clients too.

Whatever client_name you publish is what the consent screen shows the person approving the connection — so name it after the thing that is actually connecting, not after popkit. The scopes they grant there are the ceiling for that connection, and never exceed their own role.

Your own client

With the official SDK — the transport handles framing, you supply the header:

TypeScript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(new URL("https://popkit.loxily.com/api/mcp"), {
  requestInit: {
    headers: { Authorization: `Bearer ${process.env.POPKIT_MCP_TOKEN}` },
  },
});

const client = new Client({ name: "my-app", version: "1.0.0" }, { capabilities: {} });
await client.connect(transport);

const { tools } = await client.listTools();
const res = await client.callTool({ name: "get_workspace_context", arguments: {} });
console.log(res.structuredContent);

Without an SDK, every call is one POST — the exact header and envelope requirements are in Tool reference. Four things are worth getting right before you write much: the required headers, the _meta envelope, reading both application/json and SSE responses, and keeping the three error layers apart (see Errors & fixes).

This endpoint is stateless: it never issues Mcp-Session-Id, so there is no session to keep alive and nothing to delete. GET and DELETE answer 405 by design — the MCP SDKs treat that as normal.