Skip to content
  • Follow System
  • English
  • 中文

Remote MCP (Streamable HTTP)

Picora’s hosted MCP server speaks MCP Streamable HTTP at a single endpoint, authenticated with OAuth 2.1 + PKCE. There is nothing to install and no API Key on your machine: the client discovers the authorization server, opens a browser for consent, and receives a short-lived Bearer token.

Endpoint

MCP endpointPOST https://mcp.picora.me/mcp (China: https://mcp.picora.cn/mcp)
TransportMCP Streamable HTTP, stateless
Protocol versions2025-06-18 (latest) · 2025-03-26 · 2024-11-05
AuthorizationAuthorization: Bearer <oauth_access_token>
Resource metadataGET /.well-known/oauth-protected-resource (RFC 9728, unauthenticated)
HealthGET /healthz{ ok, ts, oauth_enabled }

Verified end-to-end with the official @modelcontextprotocol/sdk 1.x Client over StreamableHTTPClientTransport.

Why choose remote over stdio

stdio (@picora/mcp-server)Remote (mcp.picora.me)
InstallationNode.js ≥ 20 + npx -y @picora/mcp-serverNothing — paste a URL
AuthenticationAPI Key (sk_live_…)OAuth 2.1 authorization code + PKCE
Secret in client configYes — the key sits in a plaintext config fileNo — only short-lived tokens the client stores itself
RevocationDelete the key in the dashboardRevoke the grant per client; refresh-token replay auto-revokes the chain
Local file upload (filePath)Yes — reads straight off your diskNo — the server has no filesystem; use url or base64
Cross-devicePer-machine setupSame account, any device or web client
AvailabilityComing soon (npm package unpublished)Coming soon (not deployed)

Pick stdio when the assistant must upload files sitting on the local disk. Pick remote for web and mobile clients, shared or managed machines, and anywhere you don’t want a long-lived API Key written to a config file.

Transport behaviour

Stateless is a branch the MCP specification explicitly permits, and it shapes what a client sees:

  • No session. The server never issues Mcp-Session-Id; every request carries its own Bearer token.
  • No SSE. Responses are always application/json — the spec lets a server choose JSON or an SSE stream, and Picora chooses JSON.
  • GET /mcp and DELETE /mcp return 405 with Allow: POST. There is no server-initiated stream to open and no session to terminate. Official SDK clients handle this gracefully.
  • No JSON-RPC batching. Batching was removed in 2025-06-18; an array payload is rejected with -32600. Send one message per request.
  • Notifications get 202. A message without an id (e.g. notifications/initialized) returns HTTP 202 with an empty body and no JSON-RPC response.

Protocol version negotiation

  • Send MCP-Protocol-Version: 2025-06-18 on every request after initialize.
  • Omit the header and the server assumes 2025-03-26 — the header was introduced in 2025-06-18, so its absence implies the previous revision.
  • Send an explicit version the server doesn’t support and you get 400 with { "error": "unsupported_protocol_version", "supported": [...] }.
  • In initialize, a recognised protocolVersion is echoed back; anything else negotiates down to 2025-06-18.

Supported methods

MethodBehaviour
initializeReturns negotiated protocolVersion, capabilities.tools.listChanged: false, and serverInfo (picora-mcp)
pingEmpty result
tools/listSingle page of tool definitions with full inputSchema. Cursors are ignored — the tool set is small and fixed, so no nextCursor is returned
tools/callRuns the tool. See error mapping below

Error mapping

SituationResponse
Unknown tool nameJSON-RPC error -32602 (Invalid params)
Tool ran but failed (scope denied, quota, upstream error)JSON-RPC result with isError: true and the detail in content
Malformed JSON-32700 Parse error
Array payload / non-object payload-32600 Invalid Request
Missing or invalid Bearer tokenHTTP 401 with WWW-Authenticate (see below)
Service disabled pending auditHTTP 503 temporarily_unavailable

Note the split: a tool failing is a successful JSON-RPC call carrying isError: true, not a protocol error. Only a tool that doesn’t exist becomes -32602.

OAuth bootstrap

A standards-compliant MCP client configures itself from the URL alone — you never paste an endpoint list by hand.

  1. Client calls POST /mcp without a token. The server answers 401 with:

    WWW-Authenticate: Bearer error="invalid_token",
    error_description="...",
    resource_metadata="https://mcp.picora.me/.well-known/oauth-protected-resource"
  2. Client fetches the resource metadata (RFC 9728). It is public, cacheable for an hour, and served even while the master switch is off:

    {
    "resource": "https://mcp.picora.me",
    "authorization_servers": ["https://api.picora.me"],
    "bearer_methods_supported": ["header"],
    "scopes_supported": [
    "media.read", "media.write", "media.delete",
    "kb.read", "kb.write", "usage.read", "account.read"
    ],
    "resource_name": "Picora MCP"
    }
  3. Client discovers the authorization server at GET https://api.picora.me/.well-known/oauth-authorization-server (RFC 8414), which advertises the consent, token, registration and revocation endpoints, the JWKS URI, code_challenge_methods_supported: ["S256"], grant_types_supported: ["authorization_code", "refresh_token"], and token_endpoint_auth_methods_supported: ["none"] — Picora treats MCP clients as public clients, so PKCE is mandatory and there is no client secret.

  4. Client registers itself via RFC 7591 dynamic registration at POST /oauth/register, or uses a pre-registered client_id.

  5. Browser consent. The client opens the authorization endpoint with an S256 code_challenge; you sign in and pick which scopes to grant.

  6. Token exchange. The client swaps the authorization code plus code_verifier for an RS256-signed access token and a refresh token.

  7. Every MCP request carries the Bearer token. The server verifies the signature offline against the JWKS and checks the revocation list — no round-trip to the authorization server per call.

Refresh tokens rotate on use. Replaying a rotated refresh token revokes the entire token chain and alerts the account owner.

Client configuration

Exact wording varies between client versions; the endpoint never does.

Cursor

~/.cursor/mcp.json:

{
"mcpServers": {
"picora": {
"url": "https://mcp.picora.me/mcp"
}
}
}

Restart Cursor. The first tool call opens the OAuth consent page in your browser; approve once.

Claude Desktop

Add a custom connector in settings and paste https://mcp.picora.me/mcp. Claude Desktop performs the discovery and OAuth flow described above; approve in the browser window it opens.

Clients that only speak stdio

Bridge the remote endpoint through the community mcp-remote adapter:

{
"mcpServers": {
"picora": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.picora.me/mcp"]
}
}
}

The bridge handles the OAuth flow locally and speaks stdio to the host. It still cannot upload local filePath arguments — the remote server has no filesystem access either way.

China deployment

Replace mcp.picora.me with mcp.picora.cn; discovery points at api.picora.cn automatically. The two regions have completely separate accounts, data and tokens.

Verifying by hand

Once the endpoint is live:

Terminal window
# Discovery works without a token
curl -s https://mcp.picora.me/.well-known/oauth-protected-resource | jq
# Health, including whether OAuth is switched on
curl -s https://mcp.picora.me/healthz | jq
# Unauthenticated call → 401 carrying resource_metadata
curl -si -X POST https://mcp.picora.me/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | head -20
# Authenticated tool listing
curl -s -X POST https://mcp.picora.me/mcp \
-H "Authorization: Bearer $PICORA_OAUTH_TOKEN" \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2025-06-18' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools | length'

Until deployment, the tool catalog is still inspectable without any of this:

Terminal window
curl -s https://api.picora.me/mcp/tools.json | jq '.version, .totals'

Scopes to request

The resource metadata advertises seven scopes. Ask for the narrowest set that does the job:

GoalScopes
Read-only research assistantmedia.read, kb.read, usage.read
Assistant that uploads and editsadd media.write, kb.write
Assistant that cleans up old assetsadd media.delete — deletion is irreversible

account.read covers basic profile information and is not required by any of the 19 tools. See Concepts for how scopes map to individual tools, and the tool reference for per-tool requirements.