Full spec

RCP v0.1 — draft. Not finalized, not yet implemented in full. This is the starting point for discussion, not a committed spec.

The spec documents all three auth modes, but the first implementation only needs to actually build none and header — those cover the common cases. oauth2 stays fully specified so the wire format never has to change to add it, but building it is deliberately deferred past v1.

The one-line pitch

MCP for the case where you already just have a REST API and don’t want to run a protocol server to expose it — a server publishes a plain JSON manifest at a URL; any client fetches it, builds tools from it, and calls the endpoints it describes directly. No JSON-RPC, no persistent connection, no SDK required on the server side at all (a server can be a single static JSON file).

Why not just use MCP

MCP is the right answer when you want rich, stateful capabilities — resources a user browses, prompts a user picks, elicitation/sampling mid-call, a long-lived connection. That power has a cost: implementing an MCP server means implementing JSON-RPC, a transport (stdio or Streamable HTTP), and the base protocol’s negotiation flow, even if all you actually have is “call this REST endpoint with this shape.”

RCP is deliberately not trying to be MCP-but-open-source. See RCP vs MCP for the full comparison and a concrete checklist for which one fits your case.

Design principles

  • Zero protocol library on the server side. A conformant server can be curl-tested — there’s no handshake, no persistent socket, no client library required to implement one. A static JSON file behind a CDN is a valid, fully conformant RCP server.
  • Discovery and execution are different requests to different places. Fetching the manifest only ever tells the client what exists. Running a tool means the client makes an ordinary HTTP request straight to that tool’s own declared URL — which can be a completely different domain than the manifest itself. The manifest endpoint is a directory, not a proxy.
  • The client is the trust boundary, same principle MCP states explicitly. A server’s manifest is untrusted input until the client’s operator has explicitly registered that server.
  • Secrets never appear in the manifest, and never reach the model. A tool declares that it needs auth and how, but the actual secret value lives only in the client’s own secret store, resolved at execution time.
  • No persistence requirement on the client either. A client is allowed to treat a fetched manifest as fully ephemeral — re-fetch it on every use. Clients may cache with a TTL for performance; nothing in the protocol depends on them doing so.

Architecture

RCP has exactly two roles — Client and Server — not the three-way Host/Client/Server split MCP uses, because there’s no persistent per-server connection to hold open; a client talks to as many servers as it wants, each interaction a plain stateless request.

  • Client — the AI application. Registers servers (URL + how to authenticate to it), fetches their manifests, presents the tools they describe to a model, executes the ones the model calls.
  • Server — anything that answers GET <manifest-url> with a conformant manifest document. Has no obligation beyond that one endpoint; the tool endpoints the manifest describes can be the same server or entirely separate ones.
text
Client                             Server
------                             ------
GET  <manifest-url>          -->  200 { "rcpVersion": "0.1", "tools": [...] }
(builds callable tools from the manifest)
...model picks a tool...
<method> <tool's own url>    -->  (whatever that endpoint normally returns)

That’s the entire wire protocol. There is no other message type.

The manifest

GET <manifest-url> returns a JSON document with rcpVersion, auth, and a tools array. Full field-by-field reference at The manifest.

json
{
  "rcpVersion": "0.1",
  "auth": { "type": "none" },
  "tools": [
    {
      "name": "get_learner_profile",
      "description": "Fetches a learner's plan and progress by id.",
      "method": "GET",
      "url": "https://api.example.com/learners/{{learnerId}}/profile",
      "params": [
        { "name": "learnerId", "type": "string", "description": "The learner's id" }
      ],
      "responseMappings": { "name": "@data.name", "progress": "@data.progress.percent" }
    }
  ]
}

Resolvers — a client-side capability, not a wire concept

A tool often needs a value that shouldn’t come from the model at all — which end-user is asking, which tenant they belong to, a locale. MCP has no answer for this; inputSchema is plain JSON Schema, every property is something the client is expected to let the model fill. RCP’s answer is to keep the wire format untouched and put the mechanism entirely on the client side instead. Full detail at Resolvers.

Auth

One declaration at the top level of the manifest secures both the manifest fetch and every tool call by default. Three modes, deliberately the same tiers MCP itself supports: none, header (a static shared secret), and oauth2 (the same RFC stack as MCP’s own authorization spec). Full detail at Auth.

Client-injected headers — beyond resolvers

Separate from resolvers, a client is free to attach headers to any outgoing request that don’t correspond to any declared param at all — a correlation id, a protocol-version echo, whatever the chosen auth mode requires. A server must ignore headers it doesn’t recognize rather than rejecting the request.

Reference SDK — what it provides

Everything above is a set of behaviors a conformant client must implement — nothing requires a shared library to exist. But without one, every client ends up hand-rolling the same discovery-fetch-template-inject logic. See Client — rcp-sdk/client and Server — rcp-sdk/server for the full reference.

typescript
const rcpClient = createRcpClient({
  auth: { type: 'header', header: 'Authorization', scheme: 'Bearer', secret: /* client-held value */ },

  resolvers: {
    learnerId: (ctx) => ctx.currentUserId,
  },

  headers: {
    'X-Client-User-Id': (ctx) => ctx.currentUserId,
    'X-RCP-Version': () => '0.1',
  },
});

const tools = await rcpClient.discover(serverUrl);
const result = await rcpClient.call(tool, agentArgs, ctx);

Fitting into an agent framework

Most agent frameworks reduce to: a flat list of callable tools handed to the model. RCP only needs to plug into that half — a client resolves a manifest’s tools into whatever tool representation its framework expects and adds them to that same flat list. A framework doesn’t need to know or care that a given tool came from an RCP server rather than a hand-built one. RCP v0.1 deliberately has no equivalent of a “skills”/instructions concept — the same posture MCP itself takes toward its own optional Skills extension.

Versioning

rcpVersion is a plain string on every manifest response. A client that receives a manifest with a rcpVersion it doesn’t understand should refuse to load that server’s tools rather than guess — same explicit-over-implicit posture as MCP’s capability negotiation.

Security & trust

Adapted from MCP’s stated principles:

  • Registration is consent. A client must not fetch or execute anything from a server the client’s operator didn’t explicitly register — no automatic discovery of arbitrary URLs.
  • Tool descriptions are untrusted content from an unverified server — a client UI that lets an operator review a manifest before attaching it to a live agent is the mitigation, not a protocol-level guarantee.
  • Secrets are client-side only. A manifest declares only the shape of auth required, never a credential.
  • A server can’t see client-side conversation state. Nothing about the surrounding conversation is ever sent to a server beyond what a specific tool call’s own params/body explicitly carry.

What’s explicitly out of scope for v0.1

  • Resources, Prompts, Sampling, Elicitation, Roots — all MCP concepts with no RCP equivalent yet. If real demand shows up, they’d be additive, opt-in extensions layered on the same manifest+HTTP model, not a rewrite of it.
  • A bidirectional/streaming transport. Every RCP interaction is a plain request/response over HTTPS; there is no long-lived connection, no server-push.
  • A server-side SDK requirement. The spec is fully defined by “does GET <manifest-url> return valid JSON matching the schema.”

Open questions for discussion

  • Should rcpVersion be part of the manifest body (as drafted) or an HTTP header, so a client can reject an incompatible version without parsing the body at all?
  • Should a call to a tool with an unresolvable registered resolver fail the whole turn, or just make that one tool silently unavailable for turns with no verified caller?
  • Should the spec recommend (not require) a naming convention for commonly-resolved params — e.g. servers document their identity param as identity or userId by convention?
  • Does a server ever need to push a manifest-changed notification, or is “clients may re-fetch whenever they want” sufficient forever?
  • Reference SDK languages/priority — presumably TypeScript first, but is a second language worth it for “open protocol” credibility?

See the RCP roadmap for what’s actually built versus still open.

Related