Skip to content
Conduit

mcp server

pi-mcp

Terminal-first MCP client for Pi: user-clickable OAuth, automatic callbacks, and no browser launches.

prjct-app/pi-mcpcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • Node.js 20 or newer
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "prjct-app-pi-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "@prjct.app/pi-mcp"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 17, 2026

pi-mcp

pi-mcp — for PI Agent

A terminal-first MCP client for Pi. No automatic browsers, no MCP App rendering.

Status: development preview. Development targets Pi 0.99.1, MCP SDK 2.0.0, Node 22.22.2 (macOS). Node ≥ 22.19.


Install

pi install npm:@prjct.app/pi-mcp

Local integration

git switch develop
npm ci --ignore-scripts
npm run check
npm test
npm run build
pi --no-extensions -e ./build

--no-extensions prevents loading the existing MCP adapter alongside this one. Do not register both: they expose the same mcp tool and command. This does not modify installed packages or Pi configuration.


Configuration

Reads mcp.json files in this order (later entries replace servers entirely, no merge):

  1. $XDG_CONFIG_HOME/mcp/mcp.json (default ~/.config/mcp/mcp.json)
  2. <Pi agent directory>/mcp.json (~/.pi/agent; honors PI_CODING_AGENT_DIR)
  3. <project>/.mcp.json (only when Pi trusts the project)
  4. <project>/<Pi config directory>/mcp.json (only when trusted)
{
  "mcpServers": {
    "linear": { "url": "https://mcp.linear.app/mcp", "auth": "oauth" },
    "local": { "command": "node", "args": ["/absolute/path/to/server.js"] }
  }
}

Server fields

FieldMeaning
urlStreamable HTTP endpoint; HTTPS required except loopback HTTP
command, args, cwd, envLocal stdio process; mutually exclusive with HTTP
headersExplicit HTTP headers; supports ${ENV_VAR}
authoauth or bearer; omit for unauthenticated servers
bearerTokenEnvEnvironment variable with the bearer token
oauth.grantTypeauthorization_code (default) or client_credentials
oauth.clientIdPre-registered client ID; requires exact oauth.issuer
oauth.clientSecretEnvEnv var with OAuth client secret (only client_credentials)
oauth.issuerExpected authorization-server issuer (exact-match binding)
oauth.redirectUriHTTP loopback callback; defaults to http://127.0.0.1:32187/callback
oauth.scopeRequested scopes
protocolVersionauto (default), legacy, or 2026-07-28
requestTimeoutMsRequest deadline; defaults to 15s
disabledKeep entry visible but prevent connection/auth

${ENV_VAR} works in URLs, commands, arguments, and env values. Missing variables fail closed. Relative cwd resolves against the config file's directory.

Machine OAuth (client_credentials)

{
  "mcpServers": {
    "internal-api": {
      "url": "https://api.example.com/mcp",
      "auth": "oauth",
      "oauth": {
        "grantType": "client_credentials",
        "clientId": "pi-service",
        "clientSecretEnv": "INTERNAL_API_CLIENT_SECRET",
        "issuer": "https://auth.example.com",
        "scope": "mcp:read"
      }
    }
  }
}

Requires an exact HTTPS issuer. No redirect URIs or client metadata docs. The secret is read from the environment variable only when connecting; acquired tokens live in session memory only.

Examples: Notion, Stripe, GitHub, Supabase, Mobbin

{
  "mcpServers": {
    "notion": { "url": "https://mcp.notion.com/mcp", "auth": "oauth" },
    "stripe": { "url": "https://mcp.stripe.com", "auth": "oauth" },
    "github": { "url": "https://api.githubcopilot.com/mcp/", "auth": "bearer", "bearerTokenEnv": "GITHUB_PERSONAL_ACCESS_TOKEN" },
    "supabase": { "url": "https://mcp.supabase.com/mcp", "auth": "oauth" },
    "mobbin": { "url": "https://api.mobbin.com/mcp", "auth": "oauth" }
  }
}

Official references: Notion MCP, Stripe MCP, GitHub remote MCP, Supabase MCP, Mobbin MCP.

Stripe uses OAuth for interactive sign-in: run /mcp auth stripe and follow the authorization link. If you previously configured bearerTokenEnv, remove it when switching to "auth": "oauth", then reload Pi before authenticating. For unattended clients, Stripe also supports an agent API key through "auth": "bearer" and "bearerTokenEnv": "STRIPE_AGENT_API_KEY"; that environment variable must be set before starting Pi.

Supabase accepts ?project_ref=<ref>&read_only=true on the URL to scope the server to one project and a read-only Postgres role; features=database,docs limits the tool groups. For CI, use "auth": "bearer", "bearerTokenEnv": "SUPABASE_ACCESS_TOKEN" with a personal access token instead of OAuth.


Commands

CommandResult
/mcpInteractive manager: pick a server, see its connection and credential state, and act on it; n adds a server from its URL
/mcp statusConnection and credential state per server (does not connect)
/mcp add <url>Set up a server from its MCP endpoint or its documentation page (see below)
/mcp tools <server>Compact list of up to 20 tools (connects on demand)
/mcp connect <server>Connect now and report the advertised tool count; undoes disconnect
/mcp disconnect <server>Close the connection and refuse new ones for this session
/mcp reconnect <server>Reset the connection and connect again
/mcp auth <server>Request a fresh authorization link (also re-authenticates a signed-in server)
/mcp logout <server>Forget the server's saved OAuth credentials (asks for confirmation in the manager)

The manager shows only actions that fit the server's state: connect, list tools, reconnect, disconnect, authenticate or re-authenticate, show or cancel a pending authorization link, and sign out. Credential state is read locally (OS keyring or environment) and never triggers network calls. Signing out removes local credentials only; tokens are not revoked at the provider. It uses Pi's standard dialogs, so it works in the TUI and over RPC.

Add a server from a URL

/mcp add <url> takes either kind of URL and walks you through the rest. /mcp add alone, or n in the /mcp panel, asks for the URL.

  • MCP endpoint, for example https://mcp.svelte.dev/mcp. pi-mcp sends one MCP initialize request, with no credentials and no redirects followed, and reads how the server signs in. A JSON-RPC reply means no sign-in. A 401 with protected-resource metadata means OAuth, and you get the usual authorization link after saving. A 401 without it means a token: you are asked for the name of the environment variable that holds it, never the token itself. Then you pick a name (suggested from the host, svelte here). No model is involved.
  • Documentation page, for example https://svelte.dev/docs/ai/mcp. The page goes to the agent as a visible message. The agent reads the page, follows its links to the setup details, asks you whatever only you can decide (which setup, which account, where a token lives), and writes the entry with mcp({ action: "add", ... }).

The mcp tool description only lists add among its actions; how to use it travels in the /mcp add message and in the reply to an incomplete add call, so requests that never add a server carry no extra instructions.

You can also just ask in the chat ("connect https://mcp.svelte.dev/mcp"). An entry with only a url is checked the same way, so the agent does not need to know how the server signs in.

Before anything is saved, a dialog shows you the exact entry, the program it would start (for stdio servers) and the page it came from. Endpoints and documentation are untrusted, so there is no way to skip this step. Without an interactive session (print or JSON mode), nothing can be added.

  • The entry goes to ~/.pi/agent/mcp.json. A name defined in another configuration file is never touched. An existing name in that file is replaced only after a "Replace" confirmation.
  • The entry is checked exactly as loading checks it, before any network request. The error names unsupported fields (documentation snippets often include "type": "http") and missing environment variables, so the agent can correct the entry before you are asked. A saved entry that referenced a missing variable would stop every server from loading.
  • Secrets are never written to the file. Use bearerTokenEnv or ${VAR} references.
  • After saving, the session reloads (other servers reconnect on their next use), the server connects, and its tools are listed. If the connection fails, the entry stays saved and the reason is shown, so it can be replaced.

OAuth flow

When the agent needs OAuth credentials, it shows one link. The user clicks and approves. Pi receives the callback automatically, stores the tokens, and notifies the agent. No pasted URLs, codes, or confirmations required. Pi never opens the browser itself.

The loopback receiver exists only during authorization (max 5 minutes), bound to loopback, validating state and callback target. No widgets or scripts.


Agent tool

A single proxy tool keeps the surface small. It is reached only from codemode scripts: mcp has codemode exposure, so the model sees codemode, never a direct mcp tool. Naming mcp in --tools or defaultTools declares it directly again.

// inside a codemode script: tools.mcp(...)
await tools.mcp({ action: "status" })
await tools.mcp({ action: "tools", server: "jira", query: "search", limit: 5 })
await tools.mcp({ action: "call", server: "jira", tool: "name", args: {} })
await tools.mcp({ action: "resources", server: "local" })
await tools.mcp({ action: "templates", server: "local" })
await tools.mcp({ action: "read", server: "local", uri: "example://resource" })
await tools.mcp({ action: "prompts", server: "local" })
await tools.mcp({ action: "prompt", server: "local", prompt: "name", args: {} })
await tools.mcp({ action: "complete", server: "local", uri: "example://docs/{topic}", argument: "topic", value: "auth" })
await tools.mcp({ action: "add", server: "svelte", config: { url: "https://mcp.svelte.dev/mcp" }, uri: "https://svelte.dev/docs/ai/remote-setup" })  // sign-in is detected

When credentials are missing, the tool returns authorization_required and places one clickable authorization link in the conversation. Do not open it, poll, or ask for callbacks. Wait for the mcp-auth message. MCP tool errors become Pi errors.


Limits

ConcernLimit
Configuration64 servers, 1 MiB per file
Tool discoverySDK pagination capped at 32 pages; display defaults to 20 tools
Response cacheSession memory; 512 entries per client
Inline text50 KiB or 2,000 lines
Overflow filesPrivate; 50 MiB per session
Images4 inline, max 2 MiB base64 each (PNG/JPEG/GIF/WebP)
OAuth20s per network phase, 5min callback window
Credential locks30s wait; never steal locks automatically

Protocol and safety

  • MCP 2026-07-28 + legacy negotiation via SDK 2.0.0 over JSON-RPC 2.0.
  • No MCP App capabilities advertised. HTML never executed. App-only tools filtered.
  • Connections are lazy and resident until reset or session teardown.
  • No automatic replay of failures, polling loop, or idle reconnect.
  • Server descriptions/results are untrusted data (not user consent).
  • A configured stdio executable can spawn programs itself.

Development

npm run check          # typecheck + immutability
npm test               # offline tests (fixtures, no prod)
npm run check:package  # dry-run tarball
npm run build          # generates build/ (unpacked tarball)
python3 scripts/smoke-tui.py  # isolated real TUI smoke test

build/ is an unpacked tarball, not transpiled code. Automated tests never read real credentials or contact production MCP services. Live checks require explicit authorization.

For an authorized Linear check (read-only):

node --import tsx scripts/smoke-linear.ts --live

Architecture · Compatibility · Releases · Contributing · Changelog · MIT

↑↓ navigate openesc close