Skip to content

Commit f34c909

Browse files
chore: Update SDK to v11.1.0 and migrate tags to attributes
Update @sentry/cloudflare, core, node and react from 11.0.0-rc.0 to 11.1.0. SDK v11 streams spans, and streamed spans only carry attributes. Scope tags still reach error events, but no longer reach spans. Only organization.slug was migrated so far, so project, issue, trace and client context was missing from spans. Add setTagAndAttribute(), which sets a value as a scope tag for errors and as a scope attribute for spans, logs and metrics. Use it for every tag that the tools and the server set, and replace setOrganizationContext with it. Add initialScope.attributes next to initialScope.tags in the stdio, Cloudflare and test client Sentry.init calls. The test client also calls setConversationId, so gen_ai spans carry the conversation ID. Co-Authored-By: Jan Peer Stöcklmair <jan.peer@sentry.io>
1 parent d929248 commit f34c909

69 files changed

Lines changed: 865 additions & 644 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎docs/contributing/adding-tools.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,10 @@ async handler(params, context: ServerContext) {
164164
);
165165
}
166166

167-
// 3. Set organization telemetry for streamed spans and error events
168-
// Import from ../../telem/organization.
169-
setOrganizationContext(params.organizationSlug);
167+
// 3. Set telemetry context for streamed spans and error events
168+
// Import from ../../telem/scope.
169+
setTagAndAttribute("organization.slug", params.organizationSlug);
170+
setTagAndAttribute("project.slug", params.projectSlug);
170171

171172
// 4. Call API
172173
const data = await api.yourMethod(params);

‎docs/operations/monitoring.md‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,17 @@ export async function createTracedToolHandler<T extends ToolName>(
4444
}
4545
```
4646

47-
### Organization Context
47+
### Request Context
4848

49-
Call `setOrganizationContext(slug)` from `src/telem/organization.ts` after
50-
resolving the organization, including from URLs. It sets `organization.slug`
51-
as a scope attribute for streamed spans, logs, and metrics, and as a tag for
52-
errors. SDK v11 no longer copies scope tags onto spans.
49+
Use `setTagAndAttribute(key, value)` from `src/telem/scope.ts` for request
50+
values that must be searchable, such as `organization.slug`, `project.slug`,
51+
or `issue.id`. It sets the value as a scope attribute for streamed spans,
52+
logs, and metrics, and as a tag for errors. SDK v11 no longer copies scope
53+
tags onto spans, so do not call `setTag` from `@sentry/core` alone.
54+
55+
Set `organization.slug` after resolving the organization, including from
56+
URLs. For values set at startup, put the same keys in both
57+
`initialScope.tags` and `initialScope.attributes` of `Sentry.init`.
5358

5459
### Span Management
5560

‎packages/mcp-cloudflare/src/server/sentry.config.ts‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
1-
import * as Sentry from "@sentry/cloudflare";
21
import type { CloudflareOptions } from "@sentry/cloudflare";
2+
import * as Sentry from "@sentry/cloudflare";
33
import { sentryBeforeSend } from "@sentry/mcp-core/telem/sentry";
44
import { LIB_VERSION } from "@sentry/mcp-core/version";
55
import type { Env } from "./types";
66

77
export default function getSentryConfig(env: Env): CloudflareOptions {
88
const versionId = env.CF_VERSION_METADATA?.id;
9+
const scopeContext = {
10+
"app.server.version": LIB_VERSION,
11+
"app.upstream.host": env.SENTRY_HOST,
12+
};
913

1014
return {
1115
dsn: env.SENTRY_DSN,
1216
tracesSampleRate: 0.3,
1317
beforeSend: sentryBeforeSend,
1418
initialScope: {
15-
tags: {
16-
"app.server.version": LIB_VERSION,
17-
"app.upstream.host": env.SENTRY_HOST,
18-
},
19+
tags: scopeContext,
20+
// SDK v11 does not copy scope tags onto streamed spans.
21+
attributes: scopeContext,
1922
},
2023
...(versionId ? { release: versionId } : {}),
2124
environment:
@@ -39,6 +42,10 @@ getSentryConfig.partial = (config: Partial<CloudflareOptions>) => {
3942
...((defaultConfig.initialScope ?? {}) as any).tags,
4043
...((config.initialScope ?? {}) as any).tags,
4144
},
45+
attributes: {
46+
...((defaultConfig.initialScope ?? {}) as any).attributes,
47+
...((config.initialScope ?? {}) as any).attributes,
48+
},
4249
},
4350
};
4451
};

‎packages/mcp-core/src/server.ts‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,7 @@ import { McpServer as LegacyMcpServer } from "@modelcontextprotocol/sdk/server/m
2121
*/
2222
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2323
import { McpServer as ModernMcpServer } from "@modelcontextprotocol/server";
24-
import {
25-
getActiveSpan,
26-
type SpanAttributeValue,
27-
setTag,
28-
setUser,
29-
} from "@sentry/core";
24+
import { getActiveSpan, type SpanAttributeValue, setUser } from "@sentry/core";
3025
import { wrapMcpServerWithSentry } from "@sentry/core/server";
3126
import { isApiAuthenticationErrorDeep } from "./api-client";
3227
import { MCP_SERVER_NAME } from "./constants";
@@ -37,6 +32,7 @@ import {
3732
} from "./internal/error-handling";
3833
import type { Skill } from "./skills";
3934
import { type LogIssueOptions, logIssue } from "./telem/logging";
35+
import { setTagAndAttribute } from "./telem/scope";
4036
import {
4137
executeToolHandler,
4238
getAvailableTools,
@@ -302,15 +298,15 @@ function configureServer({
302298
setUser(user);
303299
}
304300
if (context.clientId) {
305-
setTag("client.id", context.clientId);
301+
setTagAndAttribute("client.id", context.clientId);
306302
}
307303
if (context.clientFamily) {
308-
setTag("app.client.family", context.clientFamily);
304+
setTagAndAttribute("app.client.family", context.clientFamily);
309305
}
310306
if (context.transport) {
311-
setTag("app.transport", context.transport);
307+
setTagAndAttribute("app.transport", context.transport);
312308
}
313-
setTag("app.server.mode.experimental", experimentalMode);
309+
setTagAndAttribute("app.server.mode.experimental", experimentalMode);
314310

315311
// Hoisted so both the handler path and the catch (onError) share one
316312
// narrowing instead of re-casting `params`.

‎packages/mcp-core/src/telem/organization.ts‎

Lines changed: 0 additions & 7 deletions
This file was deleted.

packages/mcp-core/src/telem/organization.test.ts renamed to packages/mcp-core/src/telem/scope.test.ts

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
1313
import { buildServer } from "../server";
1414
import { getServerContext } from "../test-setup";
1515
import findProjects from "../tools/catalog/find-projects";
16+
import type { ServerContext } from "../types";
1617

1718
const beforeSendSpan = vi.fn((span: StreamedSpanJSON) => span);
1819
const beforeSend = vi.fn((event: ErrorEvent) => event);
@@ -45,6 +46,27 @@ afterEach(async () => {
4546
getIsolationScope().setTag("organization.slug", undefined);
4647
});
4748

49+
async function callTool(
50+
context: ServerContext,
51+
name: string,
52+
args: Record<string, unknown>,
53+
): Promise<void> {
54+
const server = buildServer({ context });
55+
const client = new Client({ name: "telemetry-test", version: "1.0.0" });
56+
const [clientTransport, serverTransport] =
57+
InMemoryTransport.createLinkedPair();
58+
try {
59+
await server.connect(serverTransport);
60+
await client.connect(clientTransport);
61+
const result = await client.callTool({ name, arguments: args });
62+
expect(result.isError).not.toBe(true);
63+
} finally {
64+
await client.close();
65+
await server.close();
66+
}
67+
await sentry.flush();
68+
}
69+
4870
describe("organization telemetry", () => {
4971
it.each([
5072
{
@@ -74,25 +96,14 @@ describe("organization telemetry", () => {
7496
])(
7597
"includes $description on streamed root spans",
7698
async ({ name, args, constraints }) => {
77-
const server = buildServer({
78-
context: getServerContext({
99+
await callTool(
100+
getServerContext({
79101
constraints,
80102
grantedSkills: new Set(["inspect"]),
81103
}),
82-
});
83-
const client = new Client({ name: "telemetry-test", version: "1.0.0" });
84-
const [clientTransport, serverTransport] =
85-
InMemoryTransport.createLinkedPair();
86-
try {
87-
await server.connect(serverTransport);
88-
await client.connect(clientTransport);
89-
const result = await client.callTool({ name, arguments: args });
90-
expect(result.isError).not.toBe(true);
91-
} finally {
92-
await client.close();
93-
await server.close();
94-
}
95-
await sentry.flush();
104+
name,
105+
args,
106+
);
96107
expect(beforeSendSpan).toHaveBeenCalledWith(
97108
expect.objectContaining({
98109
is_segment: true,
@@ -126,3 +137,31 @@ describe("organization telemetry", () => {
126137
);
127138
});
128139
});
140+
141+
describe("request and tool telemetry", () => {
142+
it("includes request and tool context on streamed root spans", async () => {
143+
await callTool(
144+
getServerContext({
145+
clientId: "telemetry-test-client",
146+
grantedSkills: new Set(["inspect"]),
147+
}),
148+
"execute_sentry_tool",
149+
{
150+
name: "get_issue_breadcrumbs",
151+
arguments: {
152+
issueUrl:
153+
"https://sentry-mcp-evals.sentry.io/issues/CLOUDFLARE-MCP-41/",
154+
},
155+
},
156+
);
157+
expect(beforeSendSpan).toHaveBeenCalledWith(
158+
expect.objectContaining({
159+
is_segment: true,
160+
attributes: expect.objectContaining({
161+
"client.id": "telemetry-test-client",
162+
"issue.id": "CLOUDFLARE-MCP-41",
163+
}),
164+
}),
165+
);
166+
});
167+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { setAttribute, setTag } from "@sentry/core";
2+
3+
/**
4+
* Sets `key` on the isolation scope as a tag for error events and as an
5+
* attribute for streamed spans, logs, and metrics. SDK v11 does not copy
6+
* scope tags onto spans.
7+
*/
8+
export function setTagAndAttribute(
9+
key: string,
10+
value: string | number | boolean,
11+
): void {
12+
setAttribute(key, value);
13+
setTag(key, value);
14+
}

‎packages/mcp-core/src/tools/catalog/add-issue-note.ts‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import { z } from "zod";
2-
import { setTag } from "@sentry/core";
3-
import { setOrganizationContext } from "../../telem/organization";
4-
import { defineTool } from "../../internal/tool-helpers/define";
2+
import type { IssueComment } from "../../api-client/types";
53
import { apiServiceFromContext } from "../../internal/tool-helpers/api";
4+
import { defineTool } from "../../internal/tool-helpers/define";
65
import {
76
ensureIssueWithinProjectConstraint,
87
parseIssueParams,
98
} from "../../internal/tool-helpers/issue";
10-
import type { IssueComment } from "../../api-client/types";
11-
import type { ServerContext } from "../../types";
129
import {
1310
ParamIssueShortId,
1411
ParamIssueUrl,
1512
ParamOrganizationSlug,
1613
ParamRegionUrl,
1714
} from "../../schema";
15+
import { setTagAndAttribute } from "../../telem/scope";
16+
import type { ServerContext } from "../../types";
1817
import {
1918
formatActor,
2019
formatDate,
@@ -86,8 +85,8 @@ export default defineTool({
8685
const apiService = apiServiceFromContext(context, {
8786
regionUrl: params.regionUrl ?? context.constraints.regionUrl ?? undefined,
8887
});
89-
setOrganizationContext(parsed.organizationSlug);
90-
setTag("issue.id", parsed.issueId);
88+
setTagAndAttribute("organization.slug", parsed.organizationSlug);
89+
setTagAndAttribute("issue.id", parsed.issueId);
9190

9291
await ensureIssueWithinProjectConstraint({
9392
apiService,

‎packages/mcp-core/src/tools/catalog/add-team-to-project.ts‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
import { setTag } from "@sentry/core";
21
import { z } from "zod";
3-
import { setOrganizationContext } from "../../telem/organization";
4-
import { defineTool } from "../../internal/tool-helpers/define";
2+
import type { Team } from "../../api-client/index";
53
import { apiServiceFromContext } from "../../internal/tool-helpers/api";
4+
import { defineTool } from "../../internal/tool-helpers/define";
65
import { structuredResult } from "../../internal/tool-helpers/results";
7-
import type { Team } from "../../api-client/index";
8-
import type { ServerContext } from "../../types";
96
import {
107
ParamOrganizationSlug,
118
ParamProjectSlug,
129
ParamRegionUrl,
1310
ParamTeamSlug,
1411
} from "../../schema";
12+
import { setTagAndAttribute } from "../../telem/scope";
13+
import type { ServerContext } from "../../types";
1514

1615
const assignedTeamSchema = z.object({
1716
id: z.string(),
@@ -72,9 +71,9 @@ export default defineTool({
7271
});
7372
const organizationSlug = params.organizationSlug;
7473

75-
setOrganizationContext(organizationSlug);
76-
setTag("project.slug", params.projectSlug);
77-
setTag("team.slug", params.teamSlug);
74+
setTagAndAttribute("organization.slug", organizationSlug);
75+
setTagAndAttribute("project.slug", params.projectSlug);
76+
setTagAndAttribute("team.slug", params.teamSlug);
7877

7978
const currentTeams = await apiService.listProjectTeams({
8079
organizationSlug,

‎packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.ts‎

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
11
import { z } from "zod";
2-
import { setOrganizationContext } from "../../telem/organization";
3-
import { defineTool } from "../../internal/tool-helpers/define";
2+
import { ApiError, ApiServerError } from "../../api-client/index";
3+
import { retryWithBackoff } from "../../internal/fetch-utils";
44
import { apiServiceFromContext } from "../../internal/tool-helpers/api";
5+
import { defineTool } from "../../internal/tool-helpers/define";
56
import {
6-
parseIssueParams,
77
assertIssueWithinProjectConstraint,
8+
parseIssueParams,
89
} from "../../internal/tool-helpers/issue";
910
import {
10-
getStatusDisplayName,
11-
isTerminalStatus,
11+
getActiveAutofixTodo,
1212
getHumanInterventionGuidance,
1313
getOutputForAutofixRun,
14-
wrapSeerContent,
15-
getActiveAutofixTodo,
1614
getSeerUnsupportedIssueMessage,
15+
getStatusDisplayName,
1716
isSeerSupportedIssue,
17+
isTerminalStatus,
18+
SEER_INITIAL_RETRY_DELAY,
19+
SEER_MAX_RETRIES,
1820
SEER_POLLING_INTERVAL,
1921
SEER_TIMEOUT,
20-
SEER_MAX_RETRIES,
21-
SEER_INITIAL_RETRY_DELAY,
22+
wrapSeerContent,
2223
} from "../../internal/tool-helpers/seer";
23-
import { retryWithBackoff } from "../../internal/fetch-utils";
24-
import type { ServerContext } from "../../types";
25-
import { ApiError, ApiServerError } from "../../api-client/index";
2624
import {
27-
ParamOrganizationSlug,
28-
ParamRegionUrl,
2925
ParamIssueShortId,
3026
ParamIssueUrl,
27+
ParamOrganizationSlug,
28+
ParamRegionUrl,
3129
} from "../../schema";
30+
import { setTagAndAttribute } from "../../telem/scope";
31+
import type { ServerContext } from "../../types";
3232

3333
export default defineTool({
3434
name: "analyze_issue_with_seer",
@@ -102,7 +102,7 @@ export default defineTool({
102102
issueUrl: params.issueUrl,
103103
});
104104

105-
setOrganizationContext(orgSlug);
105+
setTagAndAttribute("organization.slug", orgSlug);
106106

107107
const issue = await apiService.getIssue({
108108
organizationSlug: orgSlug,

0 commit comments

Comments
 (0)