> ## Documentation Index
> Fetch the complete documentation index at: https://docs.epode.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Server Instrumentation with the Agent Feedback SDK

> Instrument any MCP server with one function call. Tool calls become confirmed agent interactions with an explicit protocol tool for submitting outcomes.

MCP tool calls are the highest-reliability surface in Agent Feedback. Unlike HTTP responses — which generic agents may silently ignore — MCP interactions are always classified as `confirmed` with `mcp` evidence. The `instrumentMcp` function from `@agent-feedback/node/mcp` wraps your server's `registerTool` method and registers `report_product_outcome` as a first-class protocol tool that agents can call after knowing the outcome.

## When to use

Use MCP instrumentation when your product exposes tools through the Model Context Protocol. You get:

* **`confirmed` classification** — every intercepted tool call is recorded as a confirmed agent interaction, not a best-effort HTTP opportunity.
* **Explicit feedback tool** — `report_product_outcome` is registered as a real MCP tool with a typed schema. Agents do not need to parse HTTP headers or HTML.
* **Session auto-detection** — if `ctx.sessionId` is present on the MCP context, it is used automatically without any callback.

## Installation

The MCP instrumentation ships in the same package as the Express and Fastify middleware. Install it once.

```bash theme={null}
npm install @agent-feedback/node
```

The `@modelcontextprotocol/server` package is an optional peer dependency. Install it separately.

```bash theme={null}
npm install @modelcontextprotocol/server
```

***

## Usage

Call `instrumentMcp` with your server instance and an options object. The function patches `server.registerTool` in-place and immediately registers `report_product_outcome`. It returns a `{ shutdown }` handle for graceful teardown.

```ts theme={null}
import { McpServer } from "@modelcontextprotocol/server";
import { instrumentMcp } from "@agent-feedback/node/mcp";

const server = new McpServer({ name: "my-product", version: "1.0.0" });

const { shutdown } = instrumentMcp(server, {
  apiKey: process.env.AGENT_FEEDBACK_KEY,
  customerRef: (args, ctx) => ctx.customerId as string | undefined,
  sessionRef: (args, ctx) => ctx.sessionId as string | undefined,
});

// Register your tools as normal — they are automatically wrapped
server.registerTool(
  "search",
  {
    title: "Search",
    description: "Search the product knowledge base.",
    inputSchema: { query: z.string() },
  },
  async ({ query }, ctx) => {
    const results = await doSearch(query);
    return { content: [{ type: "text", text: JSON.stringify(results) }] };
  },
);

process.on("SIGTERM", async () => {
  await shutdown();
});
```

***

## How it works

`instrumentMcp` intercepts `server.registerTool` before your own tool registrations. For every tool you register (except `report_product_outcome` itself):

1. The wrapper records a telemetry event with `surface: "mcp"`, `classification: "confirmed"`, and `confirmationMethod: "mcp"`.
2. It appends a `_agentFeedback` field to `structuredContent` containing the feedback handle, fields, and expiry.
3. It appends a plain-text reminder to the `content` array instructing the agent to call `report_product_outcome`.

The separately registered `report_product_outcome` tool receives the `feedbackHandle` from step 2, POSTs the outcome directly to the Agent Feedback API, and returns a structured confirmation. Agents that support structured content read it from there; agents that only read text content see the reminder in `content`.

<Note>
  `report_product_outcome` is never double-wrapped. `instrumentMcp` checks the tool name before patching and calls the original `registerTool` directly for that tool.
</Note>

***

## McpInstrumentationOptions

<ParamField body="apiKey" type="string" required>
  Your Agent Feedback product key. Must match the pattern `af_live_<keyId>_<secret>`. Store this in an environment variable.
</ParamField>

<ParamField body="endpoint" type="string" default="https://agent-feedback-api-production.up.railway.app">
  Override the Agent Feedback API endpoint. Useful for staging environments.
</ParamField>

<ParamField body="feedbackMode" type="&#x22;auto&#x22; | &#x22;ask&#x22; | &#x22;off&#x22;" default="&#x22;auto&#x22;">
  Controls the `required` flag in the feedback envelope sent to the agent.

  * `"auto"` — the envelope sets `required: true`; the agent is instructed to submit autonomously.
  * `"ask"` — the envelope sets `required: false`; the agent submits only when the outcome is known.
  * `"off"` — instrumentation is disabled entirely.
</ParamField>

<ParamField body="customerRef" type="(args: unknown, ctx: McpContext) => string | undefined | null">
  Callback that extracts an opaque customer identifier from the tool arguments and MCP context. Both parameters are passed; use whichever carries the account ID. Exceptions are caught and logged as warnings — they never surface to the tool caller.
</ParamField>

<ParamField body="sessionRef" type="(args: unknown, ctx: McpContext) => string | undefined | null">
  Callback that extracts a session identifier. When this callback is omitted, `instrumentMcp` falls back to `ctx.sessionId` automatically if it is a string.
</ParamField>

<ParamField body="runtimeHint" type="(args: unknown, ctx: McpContext) => string | undefined | null">
  Callback that returns a hint about the agent runtime (e.g. `"claude"`, `"gpt-4o"`). Used for runtime-level breakdown in the dashboard.
</ParamField>

***

## Session auto-detection

You do not need to provide a `sessionRef` callback if your MCP server already populates `ctx.sessionId`. `instrumentMcp` checks `ctx.sessionId` automatically and uses it as the session reference when the callback is absent or returns nothing.

```ts theme={null}
// sessionRef omitted — ctx.sessionId is used automatically
const { shutdown } = instrumentMcp(server, {
  apiKey: process.env.AGENT_FEEDBACK_KEY,
  customerRef: (args, ctx) => ctx.customerId as string | undefined,
});
```

***

## Graceful shutdown

Call `shutdown()` before your process exits to drain the telemetry queue.

```ts theme={null}
const { shutdown } = instrumentMcp(server, { apiKey: process.env.AGENT_FEEDBACK_KEY });

process.on("SIGTERM", async () => {
  await shutdown();
  process.exit(0);
});
```

***

## `report_product_outcome` tool schema

The tool is registered with the following Zod input schema and annotations:

```ts theme={null}
{
  feedbackHandle: z.string().startsWith("afr2_"),
  outcome: z.enum(["success", "partial", "failure"]),
  note: z.string().min(8).max(500),
}
```

Annotations: `readOnlyHint: false`, `destructiveHint: false`, `idempotentHint: true`.

The agent must pass the `feedbackHandle` value from the `_agentFeedback.feedbackHandle` field of the tool result — not the full authorization token. The SDK strips the `Bearer ` prefix automatically when embedding the handle in the structured content.

<Warning>
  Instruct your agents to submit exactly one outcome review per tool call and to never include prompts, transcripts, credentials, personal data, or raw product content in the `note` field. The SDK enforces the `note` length (8–500 characters) at the API level.
</Warning>

<Tip>
  MCP interactions are always classified as `confirmed` with `mcp` evidence regardless of whether the agent calls `report_product_outcome`. The telemetry event is recorded at tool-call time; the outcome review is a separate write-only receipt that updates the interaction record when received.
</Tip>
