Plane docs
Guides

Third-party MCP servers and secrets

Naming a server you do not host, and the credential it authenticates with.

An agent may call an MCP server Plane does not host and cannot sign for — Linear, Notion, whatever your team already runs. Declare it inline in the definition.

definePlane({
  agent: { /* … */ },
  mcpServers: [
    {
      name: "linear",
      url: "https://mcp.linear.app/mcp",
      auth: { type: "bearer", secret: "linear-token" },
      allow: ["list_issues", "create_issue"]
    }
  ]
})

Equivalently, in a raw definition:

{ "type": "mcp", "server": { "name": "linear", "url": "https://mcp.linear.app/mcp",
  "auth": { "type": "bearer", "secret": "linear-token" } },
  "allow": ["list_issues"] }

auth.secret is a name, never a value

The agent definition is versioned, immutable and committed to your repository. A credential has no business being in one. Register the value once, out of band:

await client.secrets.upsert({
  secret: {
    name: "linear-token",
    value: process.env.LINEAR_TOKEN!,
    description: "Linear MCP, workspace-level token"
  }
})

await client.secrets.list()      // names, descriptions and two timestamps — never a value
await client.secrets.delete({ name: "linear-token" })   // refused while an agent names it

The API has no endpoint that returns a secret's value, so the control plane's Secrets page is a table of names. Rotating one is another upsert.

plane sync verifies that every named secret exists and refuses to write a version when one does not, naming the missing ones. It never creates them, and a secret value never passes through a definition or through the SDK.

A name that does not resolve at connect time is a ConnectionError, which the turn records as a session.error and continues without those tools — rather than failing the turn.

An inline server is never signed

Signing needs a stable sub and an audience Plane controls, and somebody else's endpoint has neither. So an inline server gets its stored bearer token and nothing else; your own tools keep going through the signed connection sync registers.

Per-user credentials

auth: { type: "bearer" } is one credential for the whole environment. When the server should act as the person the session is for instead, register it as a connection with auth: { type: "oauth" } — see per-user OAuth.

On this page