Set Up MCP Monitoring for Cloudflare
Monitor MCP server tool executions, prompt retrievals, resource access, and errors.
With Sentry's MCP Monitoring, you can track and debug MCP servers with full-stack context. You can monitor tool executions, prompt retrievals, resource access, and error rates alongside your other Sentry data, including logs, errors, and traces.
Before you begin, set up tracing.
Starting with Sentry JavaScript SDK version 11.1.0, the SDK instruments every McpServer instance automatically when tracing is enabled. It records MCP requests, tool calls, prompt retrievals, resource reads, and handler errors. Automatic instrumentation supports @modelcontextprotocol/server >=2.0.0 <3 and @modelcontextprotocol/sdk >=1.9.0 <2.
If you build your Worker with the Sentry Vite plugin, create your MCP server as usual. No Sentry-specific code is needed:
mcp-server.jsimport { McpServer } from "@modelcontextprotocol/server";
const server = new McpServer({
name: "my-mcp-server",
version: "1.0.0",
});
import { McpServer } from "@modelcontextprotocol/server";
const server = new McpServer({
name: "my-mcp-server",
version: "1.0.0",
});
On a plain Wrangler build, or on SDK versions earlier than 11.1.0, import Sentry from @sentry/cloudflare and wrap the MCP server instance before connecting it to a transport:
mcp-server.jsimport * as Sentry from "@sentry/cloudflare";
import { McpServer } from "@modelcontextprotocol/server";
const server = Sentry.wrapMcpServerWithSentry(
new McpServer({
name: "my-mcp-server",
version: "1.0.0",
}),
);
import * as Sentry from "@sentry/cloudflare";
import { McpServer } from "@modelcontextprotocol/server";
const server = Sentry.wrapMcpServerWithSentry(
new McpServer({
name: "my-mcp-server",
version: "1.0.0",
}),
);
The wrapper returns the same server instance. Wrapping a server the SDK already instrumented doesn't instrument it twice.
Support for @modelcontextprotocol/server 2.x requires Sentry JavaScript SDK version 10.70.0 or newer. For @modelcontextprotocol/sdk 1.x, use Sentry JavaScript SDK version 9.46.0 or newer and import McpServer from @modelcontextprotocol/sdk/server/mcp.js. The Sentry wrapper is otherwise the same.
By default, the SDK sends the inputs and outputs of your MCP tool calls, prompt retrievals, and resource reads. This gives you rich debugging context. MCP payloads can carry sensitive data, such as tokens, file paths, or private user content. Review what your server handles before you deploy to production.
The dataCollection.genAI option controls this recording globally for both MCP and AI instrumentation:
Sentry.init({
dsn: "___PUBLIC_DSN___",
dataCollection: {
genAI: { inputs: false, outputs: false },
},
});
Sentry.init({
dsn: "___PUBLIC_DSN___",
dataCollection: {
genAI: { inputs: false, outputs: false },
},
});
To control recording for a specific server, pass it to wrapMcpServerWithSentry with the recordInputs and recordOutputs options. These options override the dataCollection.genAI settings, and they also apply to a server the SDK has already instrumented automatically:
const server = Sentry.wrapMcpServerWithSentry(mcpServer, {
recordInputs: false,
recordOutputs: false,
});
const server = Sentry.wrapMcpServerWithSentry(mcpServer, {
recordInputs: false,
recordOutputs: false,
});
The recordInputs and recordOutputs options require Sentry JavaScript SDK version 10.33.0 or newer. In version 10, the SDK records inputs and outputs only when you set the dataCollection option or enable them on the wrapper. Starting with version 11, it records them by default.
Cloudflare MCP work can finish after the Worker returns an HTTP response, including work kept alive with waitUntil(). The SDK streams spans by default, sending each sampled span as it finishes, so this work is captured without extra configuration. You still need to instrument the MCP server as shown above.
If you set traceLifecycle: "static", the SDK snapshots the request transaction when the response is returned and MCP spans that finish later are dropped. Remove the option to get them back.
Streaming sends span records instead of assembling one transaction event with embedded spans. beforeSendTransaction and ignoreTransactions don't apply to streamed spans. Use beforeSendSpan and ignoreSpans instead.
If you use McpAgent, make sure the McpServer returned by its server getter is instrumented, and that the Agent class is instrumented to preserve request and RPC context. The Vite plugin does both automatically. On a plain Wrangler build, wrap the server with wrapMcpServerWithSentry and the class with instrumentAgentWithSentry. Agent instrumentation, MCP server instrumentation, and span streaming solve different parts of the setup; none replaces the others. See Agents SDK.
The setup above instruments MCP servers built with the official MCP SDK (@modelcontextprotocol/server or @modelcontextprotocol/sdk). If your server uses a different library or a custom implementation that the SDK can't instrument, you can record the same spans manually.
You don't need this if your server is already instrumented, automatically or with wrapMcpServerWithSentry. The SDK creates these spans for you. Otherwise, use Sentry.startSpan() to create the spans described below.
Describes MCP tool execution.
- The span
op(transaction mode) or the span'ssentry.opattribute (stream mode) MUST be"mcp.server". - The span
nameSHOULD be"tools/call {mcp.tool.name}". - The
mcp.tool.nameattribute MUST be set to the tool's name. (e.g."get_weather") - The
mcp.method.nameattribute SHOULD be set to"tools/call". - All Common Span Attributes SHOULD be set.
Additional attributes on the span:
| Data Attribute | Type | Requirement Level | Description | Example |
|---|---|---|---|---|
mcp.tool.name | string | required | The name of the MCP tool being called. | "get_weather" |
mcp.method.name | string | recommended | Should be set to "tools/call". | "tools/call" |
mcp.request.id | string | optional | The unique identifier for the MCP request. | "req_123abc" |
mcp.request.argument.* | any | optional | Tool input arguments (requires send_default_pii=True). | "San Francisco" for mcp.request.argument.city |
mcp.tool.result.content | string | optional | The result/output content from the tool execution. | "The weather is sunny" |
mcp.tool.result.content_count | int | optional | The number of items/keys in the tool result. | 5 |
mcp.tool.result.is_error | boolean | optional | Whether the tool execution resulted in an error. | True |
// Example tool execution
const toolName = "get_weather";
const toolArguments = { city: "San Francisco" };
await Sentry.startSpan(
{
op: "mcp.server",
name: `tools/call ${toolName}`,
},
async (span) => {
// Set MCP-specific attributes
span.setAttribute("mcp.tool.name", toolName);
span.setAttribute("mcp.method.name", "tools/call");
// Set request metadata
span.setAttribute("mcp.request.id", "req_123abc");
span.setAttribute("mcp.session.id", "session_xyz789");
span.setAttribute("mcp.transport", "stdio"); // or "http", "sse" for HTTP/WebSocket/SSE
span.setAttribute("network.transport", "pipe"); // or "tcp" for HTTP/SSE
// Set tool arguments (optional, requires recordInputs: true)
for (const [key, value] of Object.entries(toolArguments)) {
span.setAttribute(`mcp.request.argument.${key}`, value);
}
// Execute the tool
try {
const result = executeTool(toolName, toolArguments);
// Set result data
span.setAttribute("mcp.tool.result.content", JSON.stringify(result));
span.setAttribute("mcp.tool.result.is_error", false);
// Set result content count if applicable
if (
Array.isArray(result) ||
(typeof result === "object" && result !== null)
) {
span.setAttribute(
"mcp.tool.result.content_count",
Array.isArray(result)
? result.length
: Object.keys(result).length,
);
}
} catch (error) {
span.setAttribute("mcp.tool.result.is_error", true);
throw error;
}
},
);
// Example tool execution
const toolName = "get_weather";
const toolArguments = { city: "San Francisco" };
await Sentry.startSpan(
{
op: "mcp.server",
name: `tools/call ${toolName}`,
},
async (span) => {
// Set MCP-specific attributes
span.setAttribute("mcp.tool.name", toolName);
span.setAttribute("mcp.method.name", "tools/call");
// Set request metadata
span.setAttribute("mcp.request.id", "req_123abc");
span.setAttribute("mcp.session.id", "session_xyz789");
span.setAttribute("mcp.transport", "stdio"); // or "http", "sse" for HTTP/WebSocket/SSE
span.setAttribute("network.transport", "pipe"); // or "tcp" for HTTP/SSE
// Set tool arguments (optional, requires recordInputs: true)
for (const [key, value] of Object.entries(toolArguments)) {
span.setAttribute(`mcp.request.argument.${key}`, value);
}
// Execute the tool
try {
const result = executeTool(toolName, toolArguments);
// Set result data
span.setAttribute("mcp.tool.result.content", JSON.stringify(result));
span.setAttribute("mcp.tool.result.is_error", false);
// Set result content count if applicable
if (
Array.isArray(result) ||
(typeof result === "object" && result !== null)
) {
span.setAttribute(
"mcp.tool.result.content_count",
Array.isArray(result)
? result.length
: Object.keys(result).length,
);
}
} catch (error) {
span.setAttribute("mcp.tool.result.is_error", true);
throw error;
}
},
);
Describes MCP prompt retrieval.
- The span
op(transaction mode) or the span'ssentry.opattribute (stream mode) MUST be"mcp.server". - The span
nameSHOULD be"prompts/get {mcp.prompt.name}". - The
mcp.prompt.nameattribute MUST be set to the prompt's name. (e.g."code_review") - The
mcp.method.nameattribute SHOULD be set to"prompts/get". - All Common Span Attributes SHOULD be set.
Additional attributes on the span:
| Data Attribute | Type | Requirement Level | Description | Example |
|---|---|---|---|---|
mcp.prompt.name | string | required | The name of the MCP prompt being retrieved. | "code_review" |
mcp.method.name | string | recommended | Should be set to "prompts/get". | "prompts/get" |
mcp.request.id | string | optional | The unique identifier for the MCP request. | "req_456def" |
mcp.request.argument.* | any | optional | Prompt input arguments (requires send_default_pii=True). | "python" for mcp.request.argument.language |
mcp.prompt.result.message_content | string | optional | The message content from the prompt retrieval (requires send_default_pii=True). | "Review the following code..." |
mcp.prompt.result.message_role | string | optional | The role of the message (only for single-message prompts). | "user", "assistant", "system" |
mcp.prompt.result.message_count | int | optional | The number of messages in the prompt result. | 1, 3 |
// Example prompt retrieval
const promptName = "code_review";
const promptArguments = { language: "python" };
await Sentry.startSpan(
{
op: "mcp.server",
name: `prompts/get ${promptName}`,
},
async (span) => {
// Set MCP-specific attributes
span.setAttribute("mcp.prompt.name", promptName);
span.setAttribute("mcp.method.name", "prompts/get");
// Set request metadata
span.setAttribute("mcp.request.id", "req_456def");
span.setAttribute("mcp.session.id", "session_xyz789");
span.setAttribute("mcp.transport", "http");
span.setAttribute("network.transport", "tcp");
// Set prompt arguments (optional, requires recordInputs: true)
for (const [key, value] of Object.entries(promptArguments)) {
span.setAttribute(`mcp.request.argument.${key}`, value);
}
// Retrieve the prompt
const promptResult = getPrompt(promptName, promptArguments);
// Set result data
const messages = promptResult.messages || [];
span.setAttribute("mcp.prompt.result.message_count", messages.length);
// For single-message prompts, set role and content
if (messages.length === 1) {
span.setAttribute(
"mcp.prompt.result.message_role",
messages[0].role,
);
// Content may contain sensitive data, only set if recordOutputs: true
span.setAttribute(
"mcp.prompt.result.message_content",
JSON.stringify(messages[0].content),
);
}
},
);
// Example prompt retrieval
const promptName = "code_review";
const promptArguments = { language: "python" };
await Sentry.startSpan(
{
op: "mcp.server",
name: `prompts/get ${promptName}`,
},
async (span) => {
// Set MCP-specific attributes
span.setAttribute("mcp.prompt.name", promptName);
span.setAttribute("mcp.method.name", "prompts/get");
// Set request metadata
span.setAttribute("mcp.request.id", "req_456def");
span.setAttribute("mcp.session.id", "session_xyz789");
span.setAttribute("mcp.transport", "http");
span.setAttribute("network.transport", "tcp");
// Set prompt arguments (optional, requires recordInputs: true)
for (const [key, value] of Object.entries(promptArguments)) {
span.setAttribute(`mcp.request.argument.${key}`, value);
}
// Retrieve the prompt
const promptResult = getPrompt(promptName, promptArguments);
// Set result data
const messages = promptResult.messages || [];
span.setAttribute("mcp.prompt.result.message_count", messages.length);
// For single-message prompts, set role and content
if (messages.length === 1) {
span.setAttribute(
"mcp.prompt.result.message_role",
messages[0].role,
);
// Content may contain sensitive data, only set if recordOutputs: true
span.setAttribute(
"mcp.prompt.result.message_content",
JSON.stringify(messages[0].content),
);
}
},
);
Describes MCP resource access.
- The span
op(transaction mode) or the span'ssentry.opattribute (stream mode) MUST be"mcp.server". - The span
nameSHOULD be"resources/read {mcp.resource.uri}". - The
mcp.resource.uriattribute MUST be set to the resource's URI. (e.g."file:///path/to/resource") - The
mcp.method.nameattribute SHOULD be set to"resources/read". - All Common Span Attributes SHOULD be set.
Additional attributes on the span:
| Data Attribute | Type | Requirement Level | Description | Example |
|---|---|---|---|---|
mcp.resource.uri | string | required | The URI of the MCP resource being accessed. | "file:///path/to/resource" |
mcp.method.name | string | recommended | Should be set to "resources/read" | "resources/read" |
mcp.request.id | string | optional | The unique identifier for the MCP request. | "req_789ghi" |
mcp.resource.protocol | string | optional | The protocol/scheme of the MCP resource URI. | "file", "http", "https" |
// Example resource access
const resourceUri = "file:///path/to/resource.txt";
await Sentry.startSpan(
{
op: "mcp.server",
name: `resources/read ${resourceUri}`,
},
async (span) => {
// Set MCP-specific attributes
span.setAttribute("mcp.resource.uri", resourceUri);
span.setAttribute("mcp.method.name", "resources/read");
// Set request metadata
span.setAttribute("mcp.request.id", "req_789ghi");
span.setAttribute("mcp.session.id", "session_xyz789");
span.setAttribute("mcp.transport", "http");
span.setAttribute("network.transport", "tcp");
// Access the resource
const resourceData = readResource(resourceUri);
},
);
// Example resource access
const resourceUri = "file:///path/to/resource.txt";
await Sentry.startSpan(
{
op: "mcp.server",
name: `resources/read ${resourceUri}`,
},
async (span) => {
// Set MCP-specific attributes
span.setAttribute("mcp.resource.uri", resourceUri);
span.setAttribute("mcp.method.name", "resources/read");
// Set request metadata
span.setAttribute("mcp.request.id", "req_789ghi");
span.setAttribute("mcp.session.id", "session_xyz789");
span.setAttribute("mcp.transport", "http");
span.setAttribute("network.transport", "tcp");
// Access the resource
const resourceData = readResource(resourceUri);
},
);
The following attributes are common across all MCP span types and SHOULD be set when available:
| Data Attribute | Type | Requirement Level | Description | Example |
|---|---|---|---|---|
mcp.transport | string | recommended | The transport method used for MCP communication. | "stdio", "sse", "http" |
network.transport | string | recommended | The network transport used. | "pipe", "tcp" |
mcp.session.id | string | recommended | The session identifier for the MCP connection. | "a1b2c3d4e5f6" |
mcp.request.id | string | optional | The unique identifier for the MCP request. | "req_123abc" |
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").