Skip to content

Commit 84a38cf

Browse files
committed
fixup! chore: Update SDK to v11.1.0 and migrate tags to attributes
1 parent f34c909 commit 84a38cf

61 files changed

Lines changed: 336 additions & 206 deletions

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: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,7 @@ async handler(params, context: ServerContext) {
166166

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

172171
// 4. Call API
173172
const data = await api.yourMethod(params);

‎docs/operations/monitoring.md‎

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,21 @@ export async function createTracedToolHandler<T extends ToolName>(
4646

4747
### Request Context
4848

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`.
49+
Call `setTargetTagsAndAttributes()` from `src/telem/scope.ts` after
50+
resolving the organization, including from URLs. It requires
51+
`organizationSlug` and takes optional `projectSlug`, `projectId`,
52+
`projectSlugOrId`, and `teamSlug`, so a tool can pass its `params` directly
53+
when they use these names. A numeric `projectSlugOrId` becomes `project.id`,
54+
any other value `project.slug`.
55+
56+
Use `setTagAndAttribute(key, value)` from the same file for other request
57+
values that must be searchable, such as `issue.id`. Both functions set the
58+
value as a scope attribute for streamed spans, logs, and metrics, and as a
59+
tag for errors. SDK v11 no longer copies scope tags onto spans, so do not
60+
call `setTag` from `@sentry/core` alone.
61+
62+
For values set at startup, put the same keys in both `initialScope.tags` and
63+
`initialScope.attributes` of `Sentry.init`.
5864

5965
### Span Management
6066

‎packages/mcp-core/src/telem/scope.test.ts‎

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,32 @@ import { buildServer } from "../server";
1414
import { getServerContext } from "../test-setup";
1515
import findProjects from "../tools/catalog/find-projects";
1616
import type { ServerContext } from "../types";
17+
import { type Target, setTargetTagsAndAttributes } from "./scope";
1718

1819
const beforeSendSpan = vi.fn((span: StreamedSpanJSON) => span);
1920
const beforeSend = vi.fn((event: ErrorEvent) => event);
2021
let sentry: ServerRuntimeClient;
2122

23+
// Without an async context strategy, withIsolationScope does not fork the
24+
// isolation scope, so values set by one test stay there for the next one.
25+
function resetScopeContext(): void {
26+
const scope = getIsolationScope();
27+
for (const key of [
28+
"organization.slug",
29+
"project.slug",
30+
"project.id",
31+
"team.slug",
32+
"issue.id",
33+
"client.id",
34+
]) {
35+
scope.setAttribute(key, undefined);
36+
scope.setTag(key, undefined);
37+
}
38+
}
39+
2240
beforeEach(() => {
2341
vi.clearAllMocks();
24-
getIsolationScope().setAttribute("organization.slug", undefined);
25-
getIsolationScope().setTag("organization.slug", undefined);
42+
resetScopeContext();
2643
sentry = new ServerRuntimeClient({
2744
dsn: "https://public@example.com/1",
2845
integrations: [],
@@ -42,8 +59,7 @@ beforeEach(() => {
4259
afterEach(async () => {
4360
await sentry.close();
4461
getCurrentScope().setClient(undefined);
45-
getIsolationScope().setAttribute("organization.slug", undefined);
46-
getIsolationScope().setTag("organization.slug", undefined);
62+
resetScopeContext();
4763
});
4864

4965
async function callTool(
@@ -165,3 +181,60 @@ describe("request and tool telemetry", () => {
165181
);
166182
});
167183
});
184+
185+
describe("setTargetTagsAndAttributes", () => {
186+
function scopeValuesFor(target: Target) {
187+
setTargetTagsAndAttributes(target);
188+
const { tags, attributes } = getIsolationScope().getScopeData();
189+
return { tags, attributes };
190+
}
191+
192+
it("sets organization, project, and team as tags and attributes", () => {
193+
const { tags, attributes } = scopeValuesFor({
194+
organizationSlug: "sentry",
195+
projectSlug: "javascript",
196+
projectId: 42,
197+
teamSlug: "sdk",
198+
});
199+
const expected = {
200+
"organization.slug": "sentry",
201+
"project.slug": "javascript",
202+
"project.id": "42",
203+
"team.slug": "sdk",
204+
};
205+
expect(tags).toMatchObject(expected);
206+
expect(attributes).toMatchObject(expected);
207+
});
208+
209+
it.each([
210+
{ projectSlugOrId: "42", key: "project.id", other: "project.slug" },
211+
{ projectSlugOrId: "javascript", key: "project.slug", other: "project.id" },
212+
])(
213+
"maps projectSlugOrId $projectSlugOrId to $key",
214+
({ projectSlugOrId, key, other }) => {
215+
const { tags, attributes } = scopeValuesFor({
216+
organizationSlug: "sentry",
217+
projectSlugOrId,
218+
});
219+
expect(tags[key]).toBe(projectSlugOrId);
220+
expect(attributes[key]).toBe(projectSlugOrId);
221+
expect(tags[other]).toBeUndefined();
222+
expect(attributes[other]).toBeUndefined();
223+
},
224+
);
225+
226+
it("skips empty project and team values", () => {
227+
const { tags, attributes } = scopeValuesFor({
228+
organizationSlug: "sentry",
229+
projectSlug: null,
230+
projectId: undefined,
231+
projectSlugOrId: "",
232+
teamSlug: null,
233+
});
234+
for (const key of ["project.slug", "project.id", "team.slug"]) {
235+
expect(tags[key]).toBeUndefined();
236+
expect(attributes[key]).toBeUndefined();
237+
}
238+
expect(attributes["organization.slug"]).toBe("sentry");
239+
});
240+
});

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { setAttribute, setTag } from "@sentry/core";
2+
import { isNumericId } from "../utils/slug-validation";
23

34
/**
45
* Sets `key` on the isolation scope as a tag for error events and as an
@@ -12,3 +13,39 @@ export function setTagAndAttribute(
1213
setAttribute(key, value);
1314
setTag(key, value);
1415
}
16+
17+
/** The Sentry organization, project, and team that a tool call targets. */
18+
export interface Target {
19+
organizationSlug: string;
20+
projectSlug?: string | null;
21+
/** Numeric project ID, sent as `project.id`. */
22+
projectId?: string | number | null;
23+
/**
24+
* Project reference as the user gave it: a numeric value is sent as
25+
* `project.id`, any other value as `project.slug`.
26+
*/
27+
projectSlugOrId?: string | null;
28+
teamSlug?: string | null;
29+
}
30+
31+
/**
32+
* Sets `organization.slug`, and `project.slug`, `project.id`, and `team.slug`
33+
* when they have a value, as tags and attributes through
34+
* {@link setTagAndAttribute}. Accepts tool `params` directly when they use
35+
* these field names.
36+
*/
37+
export function setTargetTagsAndAttributes(target: Target): void {
38+
const { organizationSlug, projectSlugOrId, teamSlug } = target;
39+
const isProjectId = projectSlugOrId ? isNumericId(projectSlugOrId) : false;
40+
const projectSlug =
41+
target.projectSlug ?? (isProjectId ? undefined : projectSlugOrId);
42+
const projectId =
43+
target.projectId ?? (isProjectId ? projectSlugOrId : undefined);
44+
45+
setTagAndAttribute("organization.slug", organizationSlug);
46+
if (projectSlug) setTagAndAttribute("project.slug", projectSlug);
47+
if (projectId != null && projectId !== "") {
48+
setTagAndAttribute("project.id", String(projectId));
49+
}
50+
if (teamSlug) setTagAndAttribute("team.slug", teamSlug);
51+
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import {
1212
ParamOrganizationSlug,
1313
ParamRegionUrl,
1414
} from "../../schema";
15-
import { setTagAndAttribute } from "../../telem/scope";
15+
import {
16+
setTargetTagsAndAttributes,
17+
setTagAndAttribute,
18+
} from "../../telem/scope";
1619
import type { ServerContext } from "../../types";
1720
import {
1821
formatActor,
@@ -85,7 +88,7 @@ export default defineTool({
8588
const apiService = apiServiceFromContext(context, {
8689
regionUrl: params.regionUrl ?? context.constraints.regionUrl ?? undefined,
8790
});
88-
setTagAndAttribute("organization.slug", parsed.organizationSlug);
91+
setTargetTagsAndAttributes({ organizationSlug: parsed.organizationSlug });
8992
setTagAndAttribute("issue.id", parsed.issueId);
9093

9194
await ensureIssueWithinProjectConstraint({

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
ParamRegionUrl,
1010
ParamTeamSlug,
1111
} from "../../schema";
12-
import { setTagAndAttribute } from "../../telem/scope";
12+
import { setTargetTagsAndAttributes } from "../../telem/scope";
1313
import type { ServerContext } from "../../types";
1414

1515
const assignedTeamSchema = z.object({
@@ -71,9 +71,7 @@ export default defineTool({
7171
});
7272
const organizationSlug = params.organizationSlug;
7373

74-
setTagAndAttribute("organization.slug", organizationSlug);
75-
setTagAndAttribute("project.slug", params.projectSlug);
76-
setTagAndAttribute("team.slug", params.teamSlug);
74+
setTargetTagsAndAttributes(params);
7775

7876
const currentTeams = await apiService.listProjectTeams({
7977
organizationSlug,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
ParamOrganizationSlug,
2828
ParamRegionUrl,
2929
} from "../../schema";
30-
import { setTagAndAttribute } from "../../telem/scope";
30+
import { setTargetTagsAndAttributes } from "../../telem/scope";
3131
import type { ServerContext } from "../../types";
3232

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

105-
setTagAndAttribute("organization.slug", orgSlug);
105+
setTargetTagsAndAttributes({ organizationSlug: orgSlug });
106106

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

‎packages/mcp-core/src/tools/catalog/create-alert-rule.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
ParamProjectSlug,
99
ParamRegionUrl,
1010
} from "../../schema";
11-
import { setTagAndAttribute } from "../../telem/scope";
11+
import { setTargetTagsAndAttributes } from "../../telem/scope";
1212
import type { ServerContext } from "../../types";
1313
import {
1414
alertRuleConfigFields,
@@ -76,7 +76,7 @@ export default defineTool({
7676
const api = apiServiceFromContext(context, {
7777
regionUrl: params.regionUrl ?? undefined,
7878
});
79-
setTagAndAttribute("organization.slug", params.organizationSlug);
79+
setTargetTagsAndAttributes(params);
8080
const project = context.constraints.projectSlug
8181
? await api.getProject({
8282
organizationSlug: params.organizationSlug,

‎packages/mcp-core/src/tools/catalog/create-dsn.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ParamProjectSlug,
88
ParamRegionUrl,
99
} from "../../schema";
10-
import { setTagAndAttribute } from "../../telem/scope";
10+
import { setTargetTagsAndAttributes } from "../../telem/scope";
1111
import type { ServerContext } from "../../types";
1212

1313
export const createDsnOutputSchema = z.object({
@@ -67,8 +67,7 @@ export default defineTool({
6767
});
6868
const organizationSlug = params.organizationSlug;
6969

70-
setTagAndAttribute("organization.slug", organizationSlug);
71-
setTagAndAttribute("project.slug", params.projectSlug);
70+
setTargetTagsAndAttributes(params);
7271

7372
const clientKey = await apiService.createClientKey({
7473
organizationSlug,

‎packages/mcp-core/src/tools/catalog/create-metric-monitor.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
ParamProjectSlug,
99
ParamRegionUrl,
1010
} from "../../schema";
11-
import { setTagAndAttribute } from "../../telem/scope";
11+
import { setTargetTagsAndAttributes } from "../../telem/scope";
1212
import type { ServerContext } from "../../types";
1313
import {
1414
metricMonitorCreateFields,
@@ -59,8 +59,7 @@ export default defineTool({
5959
const api = apiServiceFromContext(context, {
6060
regionUrl: params.regionUrl ?? undefined,
6161
});
62-
setTagAndAttribute("organization.slug", params.organizationSlug);
63-
setTagAndAttribute("project.slug", params.projectSlug);
62+
setTargetTagsAndAttributes(params);
6463
const { timeWindowSeconds, ...query } = params.query;
6564
const body = {
6665
type: "metric_issue" as const,

0 commit comments

Comments
 (0)