Resolvers

A resolver-bound param never reaches the model — it's removed from the tool schema at discovery time, not just hidden by convention.

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 — anything the client’s own operator already knows and the model would otherwise have to guess, or could spoof.

Nothing is declared in the manifest. A param like learnerId is written exactly like any other model-fillable param — the server doesn’t tag it, doesn’t need a reserved name, and doesn’t need to know whether some client intercepts it. The mechanism lives entirely on the client.

Configuring one

typescript
const client = createRcpClient({
  resolvers: {
    // fills a declared {{learnerId}} param before the model ever sees
    // the tool's schema — the model has no argument to fill for it
    learnerId: (ctx) => ctx.currentUserId,
  },
});

What happens at each step

  • Discovery — the client removes every param it has a registered resolver for before the tool schema is shown to the model. Structurally unfillable, not just discouraged.
  • Execution — the client calls the resolver to get the real value and substitutes it into the request before the server ever sees it.
  • No value to resolve — a resolver with nothing to resolve from (no verified caller behind this turn, say) fails the call before any HTTP request goes out.

Seeing what a server is asking for

describeManifest() is the programmatic way to check, before wiring a server into a live agent, which params it declares and which of those you already have a resolver for:

typescript
import { createRcpClient, describeManifest } from 'rcp-sdk/client';

const client = createRcpClient({ resolvers: { userId: (ctx) => ctx.currentUserId } });
const { manifest, tools } = await client.discover('https://example.com/rcp/manifest');

console.log(describeManifest(manifest, tools));
// RCP manifest v0.1 — auth: header "Authorization" (scheme: Bearer)
// 1 tool(s):
//   - get_profile (GET) — Get the current user's profile.
//       userId: string, required [resolved by client, hidden from model] — the user's id
This is deliberately general-purpose — the same mechanism covers end-user identity, a tenant id, a region, or anything else an operator wants auto-filled. A client that never configures a resolver for a given param just shows it to the model as an ordinary fillable argument — there’s no protocol-level signal warning otherwise. A server’s description on that param is the only hint a client operator gets.

Related