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.
- 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.
- 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.
- Neither, but you can set a request header → issue a
popkit_it_…key in Settings and put it inAuthorization. This is also the right answer for anything unattended. - It speaks OAuth and lets you type a
client_id→ host a small metadata document yourself and use its URL as theclient_id. That turns any OAuth-capable client into a CIMD client — see below. - None of the above → it cannot connect over OAuth. In practice this is Continue, whose MCP config has no OAuth fields at all.
| Client | Credential | Why |
|---|---|---|
| Claude Code · claude.ai | OAuth (nothing to register) | Speaks CIMD — identifies itself with a hosted metadata URL |
| Cursor | OAuth (nothing to register) | We host its client metadata document — paste one URL, no key. See below |
| Cline · Continue | popkit_it_… in a header | Cline can take a hand-filled client_id but it is undocumented; Continue has no OAuth fields at all |
| n8n · Dify | OAuth (registers itself) | Their OAuth uses DCR, which production now offers — keep the DCR switch on, do not hand-fill |
| Your own script | popkit_it_… in a header | Simplest option — though hosting your own metadata document gets you OAuth instead |
| Anything else | Run the check below | The question is whether it speaks CIMD, not which product it is |
Most clients share one JSON shape — a mcpServers map with url + headers:
{
"mcpServers": {
"popkit": {
"url": "https://popkit.loxily.com/api/mcp",
"headers": {
"Authorization": "Bearer popkit_it_YOUR_TOKEN"
}
}
}
}CIMD clients (Claude Code · claude.ai)
claude mcp add --transport http popkit https://popkit.loxily.com/api/mcpThe 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:
{
"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.
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:
{
"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/:
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 }}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 Authand paste apopkit_it_…into the single Bearer Token field.
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:
- 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. - Check its docs for
client_id. If it lets you type one in, you are on path 3 below, whatever it says about registration. - 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:
{
"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.
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:
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).
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.