Skip to content

Capabilities

Status: Implemented in v0.1 — v0.1 concepts. APIs shown are the as-built v0.1 surface.

A capability is a governed application operation derived from an oRPC procedure. It is the central abstraction of oRPC Agent — not "tool", which is only how one adapter represents a capability on one surface (ADR-002).

Why not just call procedures?

A procedure answers "what does this operation do?". Handing it to an agent raises questions the procedure alone cannot answer:

  • May this surface reach it at all? (a UI action is not automatically an MCP tool)
  • What does it do to the world — read, write, destroy, leave the system?
  • How sensitive is it, independent of side effects?
  • May it be retried? Must a human approve it? What may models see of its output?
  • Who asked, and where is that recorded?

A capability is a procedure plus those answers, made explicit and machine-readable.

Anatomy

text
capability "orders.refund"
├── identity            id (registry path), tags
├── contract            input schema, output schema      <- from the procedure
├── implementation      handler + oRPC middleware        <- from the procedure
├── description         model-facing usage text          \
├── exposure            per-surface allow map             \
├── classification      sideEffect + risk                  |  AgentMeta
├── execution policy    timeout, retry, idempotency        |  (.meta({ agent }))
├── approval policy     static gate or policy-driven      /
├── redaction           output / approvalInput hooks     /
└── adapter hints       tool names, MCP annotations     /

Everything above the line already exists in your oRPC codebase; oRPC Agent adds the metadata block and never forks the implementation (ADR-001).

Defining one

ts
import { os } from "@orpc/server";
import { agentProcedure } from "@orpc-agent/core";
import * as z from "zod";

const base = os.$context<AppContext>().use(requireSession);
const agentBase = agentProcedure(base);

export const refundOrder = agentBase
  .meta({
    agent: {
      description: "Refund an order, fully or partially. Check eligibility first.",
      expose: { aiSdk: true, direct: true },        // not on mcp
      sideEffect: "write",
      risk: "high",
      tags: ["orders", "money"],
      redact: { output: (o) => ({ ...(o as Refund), gatewayRef: undefined }) },
    },
  })
  .errors({ NOT_ELIGIBLE: { message: "Order is not eligible for refund." } })
  .input(z.object({ orderId: z.string(), amount: z.number().positive(), reason: z.string() }))
  .output(RefundResult)
  .handler(async ({ input, context, errors, signal }) => {
    if (!(await context.orders.isRefundable(input.orderId))) throw errors.NOT_ELIGIBLE();
    return context.payments.refund(input, { signal });
  });

The procedure remains a plain oRPC procedure: mount it in your router, call it from your UI, generate OpenAPI from it. The agent block only matters to createCapabilityRegistry and the runtime.

One capability, many surfaces

SurfaceRepresentationTrust profile
directruntime.invoke from your server codeYour code, your process
aiSdkAI SDK tool in your model loopYour process, model-chosen arguments
mcpMCP tool for external clientsExternal client, model-chosen arguments
workflowStep in a durable workflow (future adapter)Your infrastructure, replayed calls
testDeterministic test invocationCI

Same validation, same policies, same middleware, same audit trail on every surface — that uniformity is the product. Exposure differs per surface because trust differs per surface (ADR-004).

Granularity guidance

Good capabilities are narrow verbs over governed nouns:

  • orders.refund(orderId, amount, reason) — good: bounded, classifiable, approvable.
  • db.query(sql) — bad: unbounded power, unclassifiable side effects, unpoliceable (and a prompt-injection amplifier — see security/prompt-injection.md).
  • orders.manage(action, payload) — bad: one capability with five side-effect profiles; split it.

If you cannot assign a single honest sideEffect value, the capability is too broad.

Independent community project — not affiliated with or endorsed by the oRPC maintainers.