feat(core): support registerTool/registerResource/registerPrompt in MCP integration - #20071

Merged
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support
Apr 7, 2026
Merged

feat(core): support registerTool/registerResource/registerPrompt in MCP integration#20071
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support

Conversation

@betegon

Copy link
Copy Markdown
Member

The @modelcontextprotocol/sdk introduced registerTool, registerResource, and registerPrompt as a new API in 1.x, and in 2.x these are the only methods available — the old tool/resource/prompt names are gone.

Before this change, servers using the new API would silently get no instrumentation: validateMcpServerInstance would reject them (it only checked for the old names), so wrapMcpServerWithSentry would return the unwrapped instance. The cloudflare-mcp e2e app already used registerTool and was affected by this.

Changes

  • MCPServerInstance type now includes optional registerTool?, registerResource?, registerPrompt? alongside the legacy methods (also made legacy ones optional with @deprecated tags since 2.x removed them)
  • validateMcpServerInstance now accepts instances with either tool+resource+prompt+connect or registerTool+registerResource+registerPrompt+connect
  • wrapAllMCPHandlers conditionally wraps whichever set of methods exists on the instance
  • captureHandlerError maps registerTooltool_execution, registerResourceresource_execution, registerPromptprompt_execution
  • Unit tests added for validation and wrapping of the new method names
  • registerTool handlers added to the node-express, node-express-v5, and tsx-express e2e apps

The existing wrapMethodHandler logic (intercepts the last argument as the callback) works identically for both old and new signatures, so no changes were needed there.

  • Tests added
  • Lints and test suite passes

Closes#16666

betegonand others added 2 commits April 1, 2026 12:36
…CP integration
The @modelcontextprotocol/sdk introduced register* methods alongside the
legacy tool/resource/prompt API in 1.x, and made them the only option in 2.x.
- MCPServerInstance now accepts both old and new method names
- validateMcpServerInstance accepts servers with either API set
- wrapAllMCPHandlers instruments whichever methods are present
- captureHandlerError maps register* names to the same error categories
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
- Add createMockMcpServerWithRegisterApi() to test utilities
- Test validation accepts register*-only servers and rejects invalid ones
- Test that registerTool/registerResource/registerPrompt get wrapped
- Add registerTool handler to node-express, node-express-v5, tsx-express e2e apps
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Core

  • Support registerTool/registerResource/registerPrompt in MCP integration by betegon in #20071
  • Support embeddings in langchain by nicohrubec in #20017

Deps

  • Bump lodash.template from 4.5.0 to 4.18.1 by dependabot in #20085
  • Bump @xmldom/xmldom from 0.8.3 to 0.8.12 by dependabot in #20066

Other

  • (core, node) Portable Express integration by isaacs in #19928
  • (deno) Add denoRuntimeMetricsIntegration by chargome in #20023
  • (node, bun) Enforce minimum collection interval in runtime metrics integrations by chargome in #20068

Bug Fixes 🐛

  • (aws-serverless) Add timeout to _endSpan forceFlush to prevent Lambda hanging by logaretm in #20064
  • (cloudflare) Ensure every request instruments functions by JPeer264 in #20044
  • (core) Set span.status to error when MCP tool returns JSON-RPC error response by betegon in #20082
  • (gatsby) Fix errorHandler signature to match bundler-plugin-core API by JPeer264 in #20048

Internal Changes 🔧

Core

  • Do not emit spans for chats.create in google-genai by nicohrubec in #19990
  • Unify .do* span ops to gen_ai.generate_content by nicohrubec in #20074
  • Simplify addResponseAttributes in openai integration by nicohrubec in #20013
  • Extract shared endStreamSpan for AI integrations by nicohrubec in #20021
  • Remove provider-specific AI span attributes in favor of gen_ai attributes in sentry conventions by nicohrubec in #20011

Deps

  • Bump mshick/add-pr-comment from dd126dd8c253650d181ad9538d8b4fa218fc31e8 to e7516d74559b5514092f5b096ed29a629a1237c6 by dependabot in #20078
  • Bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.24.1 to 2.25.2 by dependabot in #20081

Other

  • (node) Add node integration tests for Vercel ToolLoopAgent by nicohrubec in #20087
  • (nuxt) Make Nuxt 5 (nightly) E2E optional by s1gr1d in #20113
  • Update validate-pr workflow by stephanie-anderson in #20072
  • Remove unused tsconfig-template folder by mydea in #20067

🤖 This preview updates automatically when you update the PR.

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser25.65 kB+0.02%+5 B 🔺
@sentry/browser - with treeshaking flags24.14 kB+0.03%+5 B 🔺
@sentry/browser (incl. Tracing)42.16 kB+0.02%+7 B 🔺
@sentry/browser (incl. Tracing, Profiling)46.77 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay)80.94 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags70.56 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)85.66 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)97.92 kB+0.01%+5 B 🔺
@sentry/browser (incl. Feedback)42.42 kB+0.02%+6 B 🔺
@sentry/browser (incl. sendFeedback)30.31 kB+0.02%+6 B 🔺
@sentry/browser (incl. FeedbackAsync)35.3 kB+0.05%+17 B 🔺
@sentry/browser (incl. Metrics)26.96 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)27.11 kB+0.03%+7 B 🔺
@sentry/browser (incl. Metrics & Logs)27.78 kB+0.03%+7 B 🔺
@sentry/react27.41 kB+0.03%+6 B 🔺
@sentry/react (incl. Tracing)44.48 kB+0.02%+5 B 🔺
@sentry/vue30.08 kB+0.02%+5 B 🔺
@sentry/vue (incl. Tracing)44.05 kB+0.02%+8 B 🔺
@sentry/svelte25.67 kB+0.02%+5 B 🔺
CDN Bundle28.33 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing)43.13 kB+0.06%+25 B 🔺
CDN Bundle (incl. Logs, Metrics)29.7 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)44.17 kB+0.05%+19 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics)68.5 kB+0.03%+17 B 🔺
CDN Bundle (incl. Tracing, Replay)80.02 kB+0.03%+19 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)81.06 kB+0.03%+18 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)85.56 kB+0.02%+15 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)86.59 kB+0.02%+16 B 🔺
CDN Bundle - uncompressed82.72 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing) - uncompressed127.86 kB+0.05%+54 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed86.86 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.27 kB+0.05%+54 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.84 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed244.74 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.13 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed257.65 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.04 kB+0.03%+54 B 🔺
@sentry/nextjs (client)46.9 kB+0.02%+7 B 🔺
@sentry/sveltekit (client)42.62 kB+0.02%+7 B 🔺
@sentry/node-core55.77 kB+0.04%+17 B 🔺
@sentry/node172.41 kB-0.2%-337 B 🔽
@sentry/node - without tracing96.05 kB+0.05%+40 B 🔺
@sentry/aws-serverless112.85 kB+0.07%+78 B 🔺

View base workflow run

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.
⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline9,220-8,855+4%
GET With Sentry1,76919%1,740+2%
GET With Sentry (error only)5,99365%6,020-0%
POST Baseline1,196-1,163+3%
POST With Sentry61652%588+5%
POST With Sentry (error only)1,07590%1,035+4%
MYSQL Baseline3,275-3,155+4%
MYSQL With Sentry48915%440+11%
MYSQL With Sentry (error only)2,60279%2,567+1%

View base workflow run

@betegon
betegon marked this pull request as ready for review April 1, 2026 12:31
betegonand others added 2 commits April 1, 2026 14:48
Call echo-register tool in SSE transport tests across node-express,
node-express-v5, and tsx-express, verifying that tools/call transactions
are recorded for handlers registered via registerTool.
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegonbetegon self-assigned this Apr 1, 2026
…ror response
When a tool handler threw an error, completeSpanWithResults() was ending
the span without setting its status to error. This caused all MCP tool
spans to appear with span.status=ok in Sentry, breaking the failure_rate()
metric in the MCP insights dashboard.
The fix passes hasError=true to completeSpanWithResults() when the
outgoing JSON-RPC response contains an error object, setting the span
status to internal_error directly on the stored span (bypassing
getActiveSpan() which doesn't return the right span at send() time).
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegon
betegonforce-pushed the feat/mcp-register-api-support branch from 68356d4 to 8984639CompareApril 1, 2026 19:37

@JPeer264JPeer264 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition. It would be nice if there would be an additional e2e test that has the v2 of the @modelcontextprotocol/sdk as well. Right now we only test against v1

@nicohrubecnicohrubec left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Potential double-wrapping when both API sets exist
    • Added sentry_wrapped marker to prevent handlers from being wrapped twice when legacy API delegates to new API.

Create PR

Or push these changes by commenting:

@cursor push 826164bfd2
Preview (826164bfd2)
diff --git a/packages/core/src/integrations/mcp-server/handlers.ts b/packages/core/src/integrations/mcp-server/handlers.ts--- a/packages/core/src/integrations/mcp-server/handlers.ts+++ b/packages/core/src/integrations/mcp-server/handlers.ts@@ -41,7 +41,13 @@
* @returns Wrapped handler function
*/
function createWrappedHandler(originalHandler: MCPHandler, methodName: keyof MCPServerInstance, handlerName: string) {
- return function (this: unknown, ...handlerArgs: unknown[]): unknown {+ // Check if handler is already wrapped to prevent double-wrapping+ // when both legacy and new API methods exist and one delegates to the other+ if ((originalHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__) {+ return originalHandler;+ }++ const wrappedHandler = function (this: unknown, ...handlerArgs: unknown[]): unknown {
try {
return createErrorCapturingHandler.call(this, originalHandler, methodName, handlerName, handlerArgs);
} catch (error) {
@@ -49,6 +55,11 @@
return originalHandler.apply(this, handlerArgs);
}
};
++ // Mark the handler as wrapped+ (wrappedHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__ = true;++ return wrappedHandler;
}
/**
diff --git a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts--- a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts+++ b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts@@ -178,4 +178,45 @@
}).not.toThrow();
});
});
++ describe('Double-wrapping prevention', () => {+ it('should not double-wrap handlers when both tool() and registerTool() exist and one delegates to the other', () => {+ // Create a mock server with both APIs where tool() delegates to registerTool()+ const registeredHandlers = new Map<string, any>();+ const mockServerWithBothApis = {+ registerTool: vi.fn((name: string, ...args: any[]) => {+ const handler = args[args.length - 1];+ registeredHandlers.set(name, handler);+ }),+ tool: vi.fn(function (this: any, name: string, handler: any) {+ // Simulate legacy tool() delegating to registerTool()+ return this.registerTool(name, {}, handler);+ }),+ resource: vi.fn(),+ prompt: vi.fn(),+ connect: vi.fn().mockResolvedValue(undefined),+ server: {+ setRequestHandler: vi.fn(),+ },+ };++ const wrapped = wrapMcpServerWithSentry(mockServerWithBothApis);++ // Register a handler via the legacy tool() method+ const originalHandler = vi.fn();+ wrapped.tool('test-tool', originalHandler);++ // Get the registered handler+ const registeredHandler = registeredHandlers.get('test-tool');++ // The handler should be wrapped (have the __sentry_wrapped__ marker)+ expect((registeredHandler as any).__sentry_wrapped__).toBe(true);++ // Verify that calling tool() only wraps once, not twice+ // If double-wrapped, the handler would have nested wrappers+ // We can verify this by checking that the registered handler is the wrapped version+ // and not a double-wrapped version+ expect(registeredHandler).not.toBe(originalHandler);+ });+ });
});

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment threadpackages/core/src/integrations/mcp-server/handlers.ts
Adds a new optional e2e test application that pins @modelcontextprotocol/sdk
v2 (split into @modelcontextprotocol/server + @modelcontextprotocol/node) and
exercises only the register* API (registerTool, registerResource, registerPrompt),
which is the only API supported in v2.
The app is marked as optional in CI since v2 is still in alpha.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
betegonand others added 2 commits April 6, 2026 08:49
…rrelation
Add mcp.transport assertion (NodeStreamableHTTPServerTransport) and
mcp.tool.result.content_count check to prove span correlation completes
with results end-to-end, matching the coverage level of the v1 tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getPlaywrightConfig starts 'node start-event-proxy.mjs' on port 3031
unconditionally. Without it the event proxy never starts and all
waitForTransaction calls hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit effc0c9. Configure here.

betegonand others added 4 commits April 6, 2026 09:10
Was copied from node-express but not updated. waitForTransaction keyed
on 'node-express-mcp-v2' would never match events from a proxy
advertising 'node-express', causing all tests to hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@modelcontextprotocol/server declares it as an optional peer dependency,
so pnpm doesn't install it automatically. Without it the app fails at
startup with ERR_MODULE_NOT_FOUND.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The app runs as ESM ("type": "module") because the MCP SDK v2 packages
are ESM-only. Sentry must be loaded via --import before the app module
to instrument Express correctly; importing it inside app.ts is too late.
Extracts Sentry.init() into instrument.mjs and starts the app with
node --import ./instrument.mjs dist/app.js, matching the pattern used
by tsx-express.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ess-mcp-v2
Without .npmrc, pnpm resolves @sentry/* from the public registry instead of
Verdaccio, so the published validateMcpServerInstance (v1-only) rejects the
v2 McpServer and no instrumentation is applied — causing the 30s timeout.
Also fix the mcp.transport assertion: NodeStreamableHTTPServerTransport
proxies onmessage to its inner WebStandardStreamableHTTPServerTransport,
so constructor.name in the wrapper is the inner class. Use the broader
/StreamableHTTPServerTransport/ regex (matching the v1 test pattern).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
s1gr1d added a commit that referenced this pull request Apr 7, 2026
Nuxt 5 is still in development and currently in a nightly version. The
E2E tests might fail due to changes but this should not block CI as long
as v5 is in nightly.
This adds the `optional` label to the test.
E.g. unblocking this PR:
#20071
@betegon
betegon merged commit 0f35883 into developApr 7, 2026
473 of 474 checks passed
@betegon
betegon deleted the feat/mcp-register-api-support branch April 7, 2026 11:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP Server - support new MCP server typescript SDK APIs

3 participants

@betegon@JPeer264@nicohrubec
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(core): support registerTool/registerResource/registerPrompt in MCP integration - #20071

Merged
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support
Apr 7, 2026
Merged

feat(core): support registerTool/registerResource/registerPrompt in MCP integration#20071
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support

Conversation

@betegon

Copy link
Copy Markdown
Member

The @modelcontextprotocol/sdk introduced registerTool, registerResource, and registerPrompt as a new API in 1.x, and in 2.x these are the only methods available — the old tool/resource/prompt names are gone.

Before this change, servers using the new API would silently get no instrumentation: validateMcpServerInstance would reject them (it only checked for the old names), so wrapMcpServerWithSentry would return the unwrapped instance. The cloudflare-mcp e2e app already used registerTool and was affected by this.

Changes

  • MCPServerInstance type now includes optional registerTool?, registerResource?, registerPrompt? alongside the legacy methods (also made legacy ones optional with @deprecated tags since 2.x removed them)
  • validateMcpServerInstance now accepts instances with either tool+resource+prompt+connect or registerTool+registerResource+registerPrompt+connect
  • wrapAllMCPHandlers conditionally wraps whichever set of methods exists on the instance
  • captureHandlerError maps registerTooltool_execution, registerResourceresource_execution, registerPromptprompt_execution
  • Unit tests added for validation and wrapping of the new method names
  • registerTool handlers added to the node-express, node-express-v5, and tsx-express e2e apps

The existing wrapMethodHandler logic (intercepts the last argument as the callback) works identically for both old and new signatures, so no changes were needed there.

  • Tests added
  • Lints and test suite passes

Closes#16666

betegonand others added 2 commits April 1, 2026 12:36
…CP integration
The @modelcontextprotocol/sdk introduced register* methods alongside the
legacy tool/resource/prompt API in 1.x, and made them the only option in 2.x.
- MCPServerInstance now accepts both old and new method names
- validateMcpServerInstance accepts servers with either API set
- wrapAllMCPHandlers instruments whichever methods are present
- captureHandlerError maps register* names to the same error categories
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
- Add createMockMcpServerWithRegisterApi() to test utilities
- Test validation accepts register*-only servers and rejects invalid ones
- Test that registerTool/registerResource/registerPrompt get wrapped
- Add registerTool handler to node-express, node-express-v5, tsx-express e2e apps
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Core

  • Support registerTool/registerResource/registerPrompt in MCP integration by betegon in #20071
  • Support embeddings in langchain by nicohrubec in #20017

Deps

  • Bump lodash.template from 4.5.0 to 4.18.1 by dependabot in #20085
  • Bump @xmldom/xmldom from 0.8.3 to 0.8.12 by dependabot in #20066

Other

  • (core, node) Portable Express integration by isaacs in #19928
  • (deno) Add denoRuntimeMetricsIntegration by chargome in #20023
  • (node, bun) Enforce minimum collection interval in runtime metrics integrations by chargome in #20068

Bug Fixes 🐛

  • (aws-serverless) Add timeout to _endSpan forceFlush to prevent Lambda hanging by logaretm in #20064
  • (cloudflare) Ensure every request instruments functions by JPeer264 in #20044
  • (core) Set span.status to error when MCP tool returns JSON-RPC error response by betegon in #20082
  • (gatsby) Fix errorHandler signature to match bundler-plugin-core API by JPeer264 in #20048

Internal Changes 🔧

Core

  • Do not emit spans for chats.create in google-genai by nicohrubec in #19990
  • Unify .do* span ops to gen_ai.generate_content by nicohrubec in #20074
  • Simplify addResponseAttributes in openai integration by nicohrubec in #20013
  • Extract shared endStreamSpan for AI integrations by nicohrubec in #20021
  • Remove provider-specific AI span attributes in favor of gen_ai attributes in sentry conventions by nicohrubec in #20011

Deps

  • Bump mshick/add-pr-comment from dd126dd8c253650d181ad9538d8b4fa218fc31e8 to e7516d74559b5514092f5b096ed29a629a1237c6 by dependabot in #20078
  • Bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.24.1 to 2.25.2 by dependabot in #20081

Other

  • (node) Add node integration tests for Vercel ToolLoopAgent by nicohrubec in #20087
  • (nuxt) Make Nuxt 5 (nightly) E2E optional by s1gr1d in #20113
  • Update validate-pr workflow by stephanie-anderson in #20072
  • Remove unused tsconfig-template folder by mydea in #20067

🤖 This preview updates automatically when you update the PR.

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser25.65 kB+0.02%+5 B 🔺
@sentry/browser - with treeshaking flags24.14 kB+0.03%+5 B 🔺
@sentry/browser (incl. Tracing)42.16 kB+0.02%+7 B 🔺
@sentry/browser (incl. Tracing, Profiling)46.77 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay)80.94 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags70.56 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)85.66 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)97.92 kB+0.01%+5 B 🔺
@sentry/browser (incl. Feedback)42.42 kB+0.02%+6 B 🔺
@sentry/browser (incl. sendFeedback)30.31 kB+0.02%+6 B 🔺
@sentry/browser (incl. FeedbackAsync)35.3 kB+0.05%+17 B 🔺
@sentry/browser (incl. Metrics)26.96 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)27.11 kB+0.03%+7 B 🔺
@sentry/browser (incl. Metrics & Logs)27.78 kB+0.03%+7 B 🔺
@sentry/react27.41 kB+0.03%+6 B 🔺
@sentry/react (incl. Tracing)44.48 kB+0.02%+5 B 🔺
@sentry/vue30.08 kB+0.02%+5 B 🔺
@sentry/vue (incl. Tracing)44.05 kB+0.02%+8 B 🔺
@sentry/svelte25.67 kB+0.02%+5 B 🔺
CDN Bundle28.33 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing)43.13 kB+0.06%+25 B 🔺
CDN Bundle (incl. Logs, Metrics)29.7 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)44.17 kB+0.05%+19 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics)68.5 kB+0.03%+17 B 🔺
CDN Bundle (incl. Tracing, Replay)80.02 kB+0.03%+19 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)81.06 kB+0.03%+18 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)85.56 kB+0.02%+15 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)86.59 kB+0.02%+16 B 🔺
CDN Bundle - uncompressed82.72 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing) - uncompressed127.86 kB+0.05%+54 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed86.86 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.27 kB+0.05%+54 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.84 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed244.74 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.13 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed257.65 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.04 kB+0.03%+54 B 🔺
@sentry/nextjs (client)46.9 kB+0.02%+7 B 🔺
@sentry/sveltekit (client)42.62 kB+0.02%+7 B 🔺
@sentry/node-core55.77 kB+0.04%+17 B 🔺
@sentry/node172.41 kB-0.2%-337 B 🔽
@sentry/node - without tracing96.05 kB+0.05%+40 B 🔺
@sentry/aws-serverless112.85 kB+0.07%+78 B 🔺

View base workflow run

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.
⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline9,220-8,855+4%
GET With Sentry1,76919%1,740+2%
GET With Sentry (error only)5,99365%6,020-0%
POST Baseline1,196-1,163+3%
POST With Sentry61652%588+5%
POST With Sentry (error only)1,07590%1,035+4%
MYSQL Baseline3,275-3,155+4%
MYSQL With Sentry48915%440+11%
MYSQL With Sentry (error only)2,60279%2,567+1%

View base workflow run

@betegon
betegon marked this pull request as ready for review April 1, 2026 12:31
betegonand others added 2 commits April 1, 2026 14:48
Call echo-register tool in SSE transport tests across node-express,
node-express-v5, and tsx-express, verifying that tools/call transactions
are recorded for handlers registered via registerTool.
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegonbetegon self-assigned this Apr 1, 2026
…ror response
When a tool handler threw an error, completeSpanWithResults() was ending
the span without setting its status to error. This caused all MCP tool
spans to appear with span.status=ok in Sentry, breaking the failure_rate()
metric in the MCP insights dashboard.
The fix passes hasError=true to completeSpanWithResults() when the
outgoing JSON-RPC response contains an error object, setting the span
status to internal_error directly on the stored span (bypassing
getActiveSpan() which doesn't return the right span at send() time).
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegon
betegonforce-pushed the feat/mcp-register-api-support branch from 68356d4 to 8984639CompareApril 1, 2026 19:37

@JPeer264JPeer264 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition. It would be nice if there would be an additional e2e test that has the v2 of the @modelcontextprotocol/sdk as well. Right now we only test against v1

@nicohrubecnicohrubec left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Potential double-wrapping when both API sets exist
    • Added sentry_wrapped marker to prevent handlers from being wrapped twice when legacy API delegates to new API.

Create PR

Or push these changes by commenting:

@cursor push 826164bfd2
Preview (826164bfd2)
diff --git a/packages/core/src/integrations/mcp-server/handlers.ts b/packages/core/src/integrations/mcp-server/handlers.ts--- a/packages/core/src/integrations/mcp-server/handlers.ts+++ b/packages/core/src/integrations/mcp-server/handlers.ts@@ -41,7 +41,13 @@
* @returns Wrapped handler function
*/
function createWrappedHandler(originalHandler: MCPHandler, methodName: keyof MCPServerInstance, handlerName: string) {
- return function (this: unknown, ...handlerArgs: unknown[]): unknown {+ // Check if handler is already wrapped to prevent double-wrapping+ // when both legacy and new API methods exist and one delegates to the other+ if ((originalHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__) {+ return originalHandler;+ }++ const wrappedHandler = function (this: unknown, ...handlerArgs: unknown[]): unknown {
try {
return createErrorCapturingHandler.call(this, originalHandler, methodName, handlerName, handlerArgs);
} catch (error) {
@@ -49,6 +55,11 @@
return originalHandler.apply(this, handlerArgs);
}
};
++ // Mark the handler as wrapped+ (wrappedHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__ = true;++ return wrappedHandler;
}
/**
diff --git a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts--- a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts+++ b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts@@ -178,4 +178,45 @@
}).not.toThrow();
});
});
++ describe('Double-wrapping prevention', () => {+ it('should not double-wrap handlers when both tool() and registerTool() exist and one delegates to the other', () => {+ // Create a mock server with both APIs where tool() delegates to registerTool()+ const registeredHandlers = new Map<string, any>();+ const mockServerWithBothApis = {+ registerTool: vi.fn((name: string, ...args: any[]) => {+ const handler = args[args.length - 1];+ registeredHandlers.set(name, handler);+ }),+ tool: vi.fn(function (this: any, name: string, handler: any) {+ // Simulate legacy tool() delegating to registerTool()+ return this.registerTool(name, {}, handler);+ }),+ resource: vi.fn(),+ prompt: vi.fn(),+ connect: vi.fn().mockResolvedValue(undefined),+ server: {+ setRequestHandler: vi.fn(),+ },+ };++ const wrapped = wrapMcpServerWithSentry(mockServerWithBothApis);++ // Register a handler via the legacy tool() method+ const originalHandler = vi.fn();+ wrapped.tool('test-tool', originalHandler);++ // Get the registered handler+ const registeredHandler = registeredHandlers.get('test-tool');++ // The handler should be wrapped (have the __sentry_wrapped__ marker)+ expect((registeredHandler as any).__sentry_wrapped__).toBe(true);++ // Verify that calling tool() only wraps once, not twice+ // If double-wrapped, the handler would have nested wrappers+ // We can verify this by checking that the registered handler is the wrapped version+ // and not a double-wrapped version+ expect(registeredHandler).not.toBe(originalHandler);+ });+ });
});

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment threadpackages/core/src/integrations/mcp-server/handlers.ts
Adds a new optional e2e test application that pins @modelcontextprotocol/sdk
v2 (split into @modelcontextprotocol/server + @modelcontextprotocol/node) and
exercises only the register* API (registerTool, registerResource, registerPrompt),
which is the only API supported in v2.
The app is marked as optional in CI since v2 is still in alpha.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
betegonand others added 2 commits April 6, 2026 08:49
…rrelation
Add mcp.transport assertion (NodeStreamableHTTPServerTransport) and
mcp.tool.result.content_count check to prove span correlation completes
with results end-to-end, matching the coverage level of the v1 tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getPlaywrightConfig starts 'node start-event-proxy.mjs' on port 3031
unconditionally. Without it the event proxy never starts and all
waitForTransaction calls hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit effc0c9. Configure here.

betegonand others added 4 commits April 6, 2026 09:10
Was copied from node-express but not updated. waitForTransaction keyed
on 'node-express-mcp-v2' would never match events from a proxy
advertising 'node-express', causing all tests to hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@modelcontextprotocol/server declares it as an optional peer dependency,
so pnpm doesn't install it automatically. Without it the app fails at
startup with ERR_MODULE_NOT_FOUND.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The app runs as ESM ("type": "module") because the MCP SDK v2 packages
are ESM-only. Sentry must be loaded via --import before the app module
to instrument Express correctly; importing it inside app.ts is too late.
Extracts Sentry.init() into instrument.mjs and starts the app with
node --import ./instrument.mjs dist/app.js, matching the pattern used
by tsx-express.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ess-mcp-v2
Without .npmrc, pnpm resolves @sentry/* from the public registry instead of
Verdaccio, so the published validateMcpServerInstance (v1-only) rejects the
v2 McpServer and no instrumentation is applied — causing the 30s timeout.
Also fix the mcp.transport assertion: NodeStreamableHTTPServerTransport
proxies onmessage to its inner WebStandardStreamableHTTPServerTransport,
so constructor.name in the wrapper is the inner class. Use the broader
/StreamableHTTPServerTransport/ regex (matching the v1 test pattern).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
s1gr1d added a commit that referenced this pull request Apr 7, 2026
Nuxt 5 is still in development and currently in a nightly version. The
E2E tests might fail due to changes but this should not block CI as long
as v5 is in nightly.
This adds the `optional` label to the test.
E.g. unblocking this PR:
#20071
@betegon
betegon merged commit 0f35883 into developApr 7, 2026
473 of 474 checks passed
@betegon
betegon deleted the feat/mcp-register-api-support branch April 7, 2026 11:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP Server - support new MCP server typescript SDK APIs

3 participants

@betegon@JPeer264@nicohrubec
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(core): support registerTool/registerResource/registerPrompt in MCP integration - #20071

Merged
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support
Apr 7, 2026
Merged

feat(core): support registerTool/registerResource/registerPrompt in MCP integration#20071
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support

Conversation

@betegon

Copy link
Copy Markdown
Member

The @modelcontextprotocol/sdk introduced registerTool, registerResource, and registerPrompt as a new API in 1.x, and in 2.x these are the only methods available — the old tool/resource/prompt names are gone.

Before this change, servers using the new API would silently get no instrumentation: validateMcpServerInstance would reject them (it only checked for the old names), so wrapMcpServerWithSentry would return the unwrapped instance. The cloudflare-mcp e2e app already used registerTool and was affected by this.

Changes

  • MCPServerInstance type now includes optional registerTool?, registerResource?, registerPrompt? alongside the legacy methods (also made legacy ones optional with @deprecated tags since 2.x removed them)
  • validateMcpServerInstance now accepts instances with either tool+resource+prompt+connect or registerTool+registerResource+registerPrompt+connect
  • wrapAllMCPHandlers conditionally wraps whichever set of methods exists on the instance
  • captureHandlerError maps registerTooltool_execution, registerResourceresource_execution, registerPromptprompt_execution
  • Unit tests added for validation and wrapping of the new method names
  • registerTool handlers added to the node-express, node-express-v5, and tsx-express e2e apps

The existing wrapMethodHandler logic (intercepts the last argument as the callback) works identically for both old and new signatures, so no changes were needed there.

  • Tests added
  • Lints and test suite passes

Closes#16666

betegonand others added 2 commits April 1, 2026 12:36
…CP integration
The @modelcontextprotocol/sdk introduced register* methods alongside the
legacy tool/resource/prompt API in 1.x, and made them the only option in 2.x.
- MCPServerInstance now accepts both old and new method names
- validateMcpServerInstance accepts servers with either API set
- wrapAllMCPHandlers instruments whichever methods are present
- captureHandlerError maps register* names to the same error categories
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
- Add createMockMcpServerWithRegisterApi() to test utilities
- Test validation accepts register*-only servers and rejects invalid ones
- Test that registerTool/registerResource/registerPrompt get wrapped
- Add registerTool handler to node-express, node-express-v5, tsx-express e2e apps
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Core

  • Support registerTool/registerResource/registerPrompt in MCP integration by betegon in #20071
  • Support embeddings in langchain by nicohrubec in #20017

Deps

  • Bump lodash.template from 4.5.0 to 4.18.1 by dependabot in #20085
  • Bump @xmldom/xmldom from 0.8.3 to 0.8.12 by dependabot in #20066

Other

  • (core, node) Portable Express integration by isaacs in #19928
  • (deno) Add denoRuntimeMetricsIntegration by chargome in #20023
  • (node, bun) Enforce minimum collection interval in runtime metrics integrations by chargome in #20068

Bug Fixes 🐛

  • (aws-serverless) Add timeout to _endSpan forceFlush to prevent Lambda hanging by logaretm in #20064
  • (cloudflare) Ensure every request instruments functions by JPeer264 in #20044
  • (core) Set span.status to error when MCP tool returns JSON-RPC error response by betegon in #20082
  • (gatsby) Fix errorHandler signature to match bundler-plugin-core API by JPeer264 in #20048

Internal Changes 🔧

Core

  • Do not emit spans for chats.create in google-genai by nicohrubec in #19990
  • Unify .do* span ops to gen_ai.generate_content by nicohrubec in #20074
  • Simplify addResponseAttributes in openai integration by nicohrubec in #20013
  • Extract shared endStreamSpan for AI integrations by nicohrubec in #20021
  • Remove provider-specific AI span attributes in favor of gen_ai attributes in sentry conventions by nicohrubec in #20011

Deps

  • Bump mshick/add-pr-comment from dd126dd8c253650d181ad9538d8b4fa218fc31e8 to e7516d74559b5514092f5b096ed29a629a1237c6 by dependabot in #20078
  • Bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.24.1 to 2.25.2 by dependabot in #20081

Other

  • (node) Add node integration tests for Vercel ToolLoopAgent by nicohrubec in #20087
  • (nuxt) Make Nuxt 5 (nightly) E2E optional by s1gr1d in #20113
  • Update validate-pr workflow by stephanie-anderson in #20072
  • Remove unused tsconfig-template folder by mydea in #20067

🤖 This preview updates automatically when you update the PR.

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser25.65 kB+0.02%+5 B 🔺
@sentry/browser - with treeshaking flags24.14 kB+0.03%+5 B 🔺
@sentry/browser (incl. Tracing)42.16 kB+0.02%+7 B 🔺
@sentry/browser (incl. Tracing, Profiling)46.77 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay)80.94 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags70.56 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)85.66 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)97.92 kB+0.01%+5 B 🔺
@sentry/browser (incl. Feedback)42.42 kB+0.02%+6 B 🔺
@sentry/browser (incl. sendFeedback)30.31 kB+0.02%+6 B 🔺
@sentry/browser (incl. FeedbackAsync)35.3 kB+0.05%+17 B 🔺
@sentry/browser (incl. Metrics)26.96 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)27.11 kB+0.03%+7 B 🔺
@sentry/browser (incl. Metrics & Logs)27.78 kB+0.03%+7 B 🔺
@sentry/react27.41 kB+0.03%+6 B 🔺
@sentry/react (incl. Tracing)44.48 kB+0.02%+5 B 🔺
@sentry/vue30.08 kB+0.02%+5 B 🔺
@sentry/vue (incl. Tracing)44.05 kB+0.02%+8 B 🔺
@sentry/svelte25.67 kB+0.02%+5 B 🔺
CDN Bundle28.33 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing)43.13 kB+0.06%+25 B 🔺
CDN Bundle (incl. Logs, Metrics)29.7 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)44.17 kB+0.05%+19 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics)68.5 kB+0.03%+17 B 🔺
CDN Bundle (incl. Tracing, Replay)80.02 kB+0.03%+19 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)81.06 kB+0.03%+18 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)85.56 kB+0.02%+15 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)86.59 kB+0.02%+16 B 🔺
CDN Bundle - uncompressed82.72 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing) - uncompressed127.86 kB+0.05%+54 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed86.86 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.27 kB+0.05%+54 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.84 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed244.74 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.13 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed257.65 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.04 kB+0.03%+54 B 🔺
@sentry/nextjs (client)46.9 kB+0.02%+7 B 🔺
@sentry/sveltekit (client)42.62 kB+0.02%+7 B 🔺
@sentry/node-core55.77 kB+0.04%+17 B 🔺
@sentry/node172.41 kB-0.2%-337 B 🔽
@sentry/node - without tracing96.05 kB+0.05%+40 B 🔺
@sentry/aws-serverless112.85 kB+0.07%+78 B 🔺

View base workflow run

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.
⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline9,220-8,855+4%
GET With Sentry1,76919%1,740+2%
GET With Sentry (error only)5,99365%6,020-0%
POST Baseline1,196-1,163+3%
POST With Sentry61652%588+5%
POST With Sentry (error only)1,07590%1,035+4%
MYSQL Baseline3,275-3,155+4%
MYSQL With Sentry48915%440+11%
MYSQL With Sentry (error only)2,60279%2,567+1%

View base workflow run

@betegon
betegon marked this pull request as ready for review April 1, 2026 12:31
betegonand others added 2 commits April 1, 2026 14:48
Call echo-register tool in SSE transport tests across node-express,
node-express-v5, and tsx-express, verifying that tools/call transactions
are recorded for handlers registered via registerTool.
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegonbetegon self-assigned this Apr 1, 2026
…ror response
When a tool handler threw an error, completeSpanWithResults() was ending
the span without setting its status to error. This caused all MCP tool
spans to appear with span.status=ok in Sentry, breaking the failure_rate()
metric in the MCP insights dashboard.
The fix passes hasError=true to completeSpanWithResults() when the
outgoing JSON-RPC response contains an error object, setting the span
status to internal_error directly on the stored span (bypassing
getActiveSpan() which doesn't return the right span at send() time).
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegon
betegonforce-pushed the feat/mcp-register-api-support branch from 68356d4 to 8984639CompareApril 1, 2026 19:37

@JPeer264JPeer264 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition. It would be nice if there would be an additional e2e test that has the v2 of the @modelcontextprotocol/sdk as well. Right now we only test against v1

@nicohrubecnicohrubec left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Potential double-wrapping when both API sets exist
    • Added sentry_wrapped marker to prevent handlers from being wrapped twice when legacy API delegates to new API.

Create PR

Or push these changes by commenting:

@cursor push 826164bfd2
Preview (826164bfd2)
diff --git a/packages/core/src/integrations/mcp-server/handlers.ts b/packages/core/src/integrations/mcp-server/handlers.ts--- a/packages/core/src/integrations/mcp-server/handlers.ts+++ b/packages/core/src/integrations/mcp-server/handlers.ts@@ -41,7 +41,13 @@
* @returns Wrapped handler function
*/
function createWrappedHandler(originalHandler: MCPHandler, methodName: keyof MCPServerInstance, handlerName: string) {
- return function (this: unknown, ...handlerArgs: unknown[]): unknown {+ // Check if handler is already wrapped to prevent double-wrapping+ // when both legacy and new API methods exist and one delegates to the other+ if ((originalHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__) {+ return originalHandler;+ }++ const wrappedHandler = function (this: unknown, ...handlerArgs: unknown[]): unknown {
try {
return createErrorCapturingHandler.call(this, originalHandler, methodName, handlerName, handlerArgs);
} catch (error) {
@@ -49,6 +55,11 @@
return originalHandler.apply(this, handlerArgs);
}
};
++ // Mark the handler as wrapped+ (wrappedHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__ = true;++ return wrappedHandler;
}
/**
diff --git a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts--- a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts+++ b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts@@ -178,4 +178,45 @@
}).not.toThrow();
});
});
++ describe('Double-wrapping prevention', () => {+ it('should not double-wrap handlers when both tool() and registerTool() exist and one delegates to the other', () => {+ // Create a mock server with both APIs where tool() delegates to registerTool()+ const registeredHandlers = new Map<string, any>();+ const mockServerWithBothApis = {+ registerTool: vi.fn((name: string, ...args: any[]) => {+ const handler = args[args.length - 1];+ registeredHandlers.set(name, handler);+ }),+ tool: vi.fn(function (this: any, name: string, handler: any) {+ // Simulate legacy tool() delegating to registerTool()+ return this.registerTool(name, {}, handler);+ }),+ resource: vi.fn(),+ prompt: vi.fn(),+ connect: vi.fn().mockResolvedValue(undefined),+ server: {+ setRequestHandler: vi.fn(),+ },+ };++ const wrapped = wrapMcpServerWithSentry(mockServerWithBothApis);++ // Register a handler via the legacy tool() method+ const originalHandler = vi.fn();+ wrapped.tool('test-tool', originalHandler);++ // Get the registered handler+ const registeredHandler = registeredHandlers.get('test-tool');++ // The handler should be wrapped (have the __sentry_wrapped__ marker)+ expect((registeredHandler as any).__sentry_wrapped__).toBe(true);++ // Verify that calling tool() only wraps once, not twice+ // If double-wrapped, the handler would have nested wrappers+ // We can verify this by checking that the registered handler is the wrapped version+ // and not a double-wrapped version+ expect(registeredHandler).not.toBe(originalHandler);+ });+ });
});

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment threadpackages/core/src/integrations/mcp-server/handlers.ts
Adds a new optional e2e test application that pins @modelcontextprotocol/sdk
v2 (split into @modelcontextprotocol/server + @modelcontextprotocol/node) and
exercises only the register* API (registerTool, registerResource, registerPrompt),
which is the only API supported in v2.
The app is marked as optional in CI since v2 is still in alpha.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
betegonand others added 2 commits April 6, 2026 08:49
…rrelation
Add mcp.transport assertion (NodeStreamableHTTPServerTransport) and
mcp.tool.result.content_count check to prove span correlation completes
with results end-to-end, matching the coverage level of the v1 tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getPlaywrightConfig starts 'node start-event-proxy.mjs' on port 3031
unconditionally. Without it the event proxy never starts and all
waitForTransaction calls hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit effc0c9. Configure here.

betegonand others added 4 commits April 6, 2026 09:10
Was copied from node-express but not updated. waitForTransaction keyed
on 'node-express-mcp-v2' would never match events from a proxy
advertising 'node-express', causing all tests to hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@modelcontextprotocol/server declares it as an optional peer dependency,
so pnpm doesn't install it automatically. Without it the app fails at
startup with ERR_MODULE_NOT_FOUND.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The app runs as ESM ("type": "module") because the MCP SDK v2 packages
are ESM-only. Sentry must be loaded via --import before the app module
to instrument Express correctly; importing it inside app.ts is too late.
Extracts Sentry.init() into instrument.mjs and starts the app with
node --import ./instrument.mjs dist/app.js, matching the pattern used
by tsx-express.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ess-mcp-v2
Without .npmrc, pnpm resolves @sentry/* from the public registry instead of
Verdaccio, so the published validateMcpServerInstance (v1-only) rejects the
v2 McpServer and no instrumentation is applied — causing the 30s timeout.
Also fix the mcp.transport assertion: NodeStreamableHTTPServerTransport
proxies onmessage to its inner WebStandardStreamableHTTPServerTransport,
so constructor.name in the wrapper is the inner class. Use the broader
/StreamableHTTPServerTransport/ regex (matching the v1 test pattern).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
s1gr1d added a commit that referenced this pull request Apr 7, 2026
Nuxt 5 is still in development and currently in a nightly version. The
E2E tests might fail due to changes but this should not block CI as long
as v5 is in nightly.
This adds the `optional` label to the test.
E.g. unblocking this PR:
#20071
@betegon
betegon merged commit 0f35883 into developApr 7, 2026
473 of 474 checks passed
@betegon
betegon deleted the feat/mcp-register-api-support branch April 7, 2026 11:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP Server - support new MCP server typescript SDK APIs

3 participants

@betegon@JPeer264@nicohrubec
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(core): support registerTool/registerResource/registerPrompt in MCP integration - #20071

Merged
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support
Apr 7, 2026
Merged

feat(core): support registerTool/registerResource/registerPrompt in MCP integration#20071
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support

Conversation

@betegon

Copy link
Copy Markdown
Member

The @modelcontextprotocol/sdk introduced registerTool, registerResource, and registerPrompt as a new API in 1.x, and in 2.x these are the only methods available — the old tool/resource/prompt names are gone.

Before this change, servers using the new API would silently get no instrumentation: validateMcpServerInstance would reject them (it only checked for the old names), so wrapMcpServerWithSentry would return the unwrapped instance. The cloudflare-mcp e2e app already used registerTool and was affected by this.

Changes

  • MCPServerInstance type now includes optional registerTool?, registerResource?, registerPrompt? alongside the legacy methods (also made legacy ones optional with @deprecated tags since 2.x removed them)
  • validateMcpServerInstance now accepts instances with either tool+resource+prompt+connect or registerTool+registerResource+registerPrompt+connect
  • wrapAllMCPHandlers conditionally wraps whichever set of methods exists on the instance
  • captureHandlerError maps registerTooltool_execution, registerResourceresource_execution, registerPromptprompt_execution
  • Unit tests added for validation and wrapping of the new method names
  • registerTool handlers added to the node-express, node-express-v5, and tsx-express e2e apps

The existing wrapMethodHandler logic (intercepts the last argument as the callback) works identically for both old and new signatures, so no changes were needed there.

  • Tests added
  • Lints and test suite passes

Closes#16666

betegonand others added 2 commits April 1, 2026 12:36
…CP integration
The @modelcontextprotocol/sdk introduced register* methods alongside the
legacy tool/resource/prompt API in 1.x, and made them the only option in 2.x.
- MCPServerInstance now accepts both old and new method names
- validateMcpServerInstance accepts servers with either API set
- wrapAllMCPHandlers instruments whichever methods are present
- captureHandlerError maps register* names to the same error categories
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
- Add createMockMcpServerWithRegisterApi() to test utilities
- Test validation accepts register*-only servers and rejects invalid ones
- Test that registerTool/registerResource/registerPrompt get wrapped
- Add registerTool handler to node-express, node-express-v5, tsx-express e2e apps
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Core

  • Support registerTool/registerResource/registerPrompt in MCP integration by betegon in #20071
  • Support embeddings in langchain by nicohrubec in #20017

Deps

  • Bump lodash.template from 4.5.0 to 4.18.1 by dependabot in #20085
  • Bump @xmldom/xmldom from 0.8.3 to 0.8.12 by dependabot in #20066

Other

  • (core, node) Portable Express integration by isaacs in #19928
  • (deno) Add denoRuntimeMetricsIntegration by chargome in #20023
  • (node, bun) Enforce minimum collection interval in runtime metrics integrations by chargome in #20068

Bug Fixes 🐛

  • (aws-serverless) Add timeout to _endSpan forceFlush to prevent Lambda hanging by logaretm in #20064
  • (cloudflare) Ensure every request instruments functions by JPeer264 in #20044
  • (core) Set span.status to error when MCP tool returns JSON-RPC error response by betegon in #20082
  • (gatsby) Fix errorHandler signature to match bundler-plugin-core API by JPeer264 in #20048

Internal Changes 🔧

Core

  • Do not emit spans for chats.create in google-genai by nicohrubec in #19990
  • Unify .do* span ops to gen_ai.generate_content by nicohrubec in #20074
  • Simplify addResponseAttributes in openai integration by nicohrubec in #20013
  • Extract shared endStreamSpan for AI integrations by nicohrubec in #20021
  • Remove provider-specific AI span attributes in favor of gen_ai attributes in sentry conventions by nicohrubec in #20011

Deps

  • Bump mshick/add-pr-comment from dd126dd8c253650d181ad9538d8b4fa218fc31e8 to e7516d74559b5514092f5b096ed29a629a1237c6 by dependabot in #20078
  • Bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.24.1 to 2.25.2 by dependabot in #20081

Other

  • (node) Add node integration tests for Vercel ToolLoopAgent by nicohrubec in #20087
  • (nuxt) Make Nuxt 5 (nightly) E2E optional by s1gr1d in #20113
  • Update validate-pr workflow by stephanie-anderson in #20072
  • Remove unused tsconfig-template folder by mydea in #20067

🤖 This preview updates automatically when you update the PR.

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser25.65 kB+0.02%+5 B 🔺
@sentry/browser - with treeshaking flags24.14 kB+0.03%+5 B 🔺
@sentry/browser (incl. Tracing)42.16 kB+0.02%+7 B 🔺
@sentry/browser (incl. Tracing, Profiling)46.77 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay)80.94 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags70.56 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)85.66 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)97.92 kB+0.01%+5 B 🔺
@sentry/browser (incl. Feedback)42.42 kB+0.02%+6 B 🔺
@sentry/browser (incl. sendFeedback)30.31 kB+0.02%+6 B 🔺
@sentry/browser (incl. FeedbackAsync)35.3 kB+0.05%+17 B 🔺
@sentry/browser (incl. Metrics)26.96 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)27.11 kB+0.03%+7 B 🔺
@sentry/browser (incl. Metrics & Logs)27.78 kB+0.03%+7 B 🔺
@sentry/react27.41 kB+0.03%+6 B 🔺
@sentry/react (incl. Tracing)44.48 kB+0.02%+5 B 🔺
@sentry/vue30.08 kB+0.02%+5 B 🔺
@sentry/vue (incl. Tracing)44.05 kB+0.02%+8 B 🔺
@sentry/svelte25.67 kB+0.02%+5 B 🔺
CDN Bundle28.33 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing)43.13 kB+0.06%+25 B 🔺
CDN Bundle (incl. Logs, Metrics)29.7 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)44.17 kB+0.05%+19 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics)68.5 kB+0.03%+17 B 🔺
CDN Bundle (incl. Tracing, Replay)80.02 kB+0.03%+19 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)81.06 kB+0.03%+18 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)85.56 kB+0.02%+15 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)86.59 kB+0.02%+16 B 🔺
CDN Bundle - uncompressed82.72 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing) - uncompressed127.86 kB+0.05%+54 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed86.86 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.27 kB+0.05%+54 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.84 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed244.74 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.13 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed257.65 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.04 kB+0.03%+54 B 🔺
@sentry/nextjs (client)46.9 kB+0.02%+7 B 🔺
@sentry/sveltekit (client)42.62 kB+0.02%+7 B 🔺
@sentry/node-core55.77 kB+0.04%+17 B 🔺
@sentry/node172.41 kB-0.2%-337 B 🔽
@sentry/node - without tracing96.05 kB+0.05%+40 B 🔺
@sentry/aws-serverless112.85 kB+0.07%+78 B 🔺

View base workflow run

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.
⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline9,220-8,855+4%
GET With Sentry1,76919%1,740+2%
GET With Sentry (error only)5,99365%6,020-0%
POST Baseline1,196-1,163+3%
POST With Sentry61652%588+5%
POST With Sentry (error only)1,07590%1,035+4%
MYSQL Baseline3,275-3,155+4%
MYSQL With Sentry48915%440+11%
MYSQL With Sentry (error only)2,60279%2,567+1%

View base workflow run

@betegon
betegon marked this pull request as ready for review April 1, 2026 12:31
betegonand others added 2 commits April 1, 2026 14:48
Call echo-register tool in SSE transport tests across node-express,
node-express-v5, and tsx-express, verifying that tools/call transactions
are recorded for handlers registered via registerTool.
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegonbetegon self-assigned this Apr 1, 2026
…ror response
When a tool handler threw an error, completeSpanWithResults() was ending
the span without setting its status to error. This caused all MCP tool
spans to appear with span.status=ok in Sentry, breaking the failure_rate()
metric in the MCP insights dashboard.
The fix passes hasError=true to completeSpanWithResults() when the
outgoing JSON-RPC response contains an error object, setting the span
status to internal_error directly on the stored span (bypassing
getActiveSpan() which doesn't return the right span at send() time).
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegon
betegonforce-pushed the feat/mcp-register-api-support branch from 68356d4 to 8984639CompareApril 1, 2026 19:37

@JPeer264JPeer264 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition. It would be nice if there would be an additional e2e test that has the v2 of the @modelcontextprotocol/sdk as well. Right now we only test against v1

@nicohrubecnicohrubec left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Potential double-wrapping when both API sets exist
    • Added sentry_wrapped marker to prevent handlers from being wrapped twice when legacy API delegates to new API.

Create PR

Or push these changes by commenting:

@cursor push 826164bfd2
Preview (826164bfd2)
diff --git a/packages/core/src/integrations/mcp-server/handlers.ts b/packages/core/src/integrations/mcp-server/handlers.ts--- a/packages/core/src/integrations/mcp-server/handlers.ts+++ b/packages/core/src/integrations/mcp-server/handlers.ts@@ -41,7 +41,13 @@
* @returns Wrapped handler function
*/
function createWrappedHandler(originalHandler: MCPHandler, methodName: keyof MCPServerInstance, handlerName: string) {
- return function (this: unknown, ...handlerArgs: unknown[]): unknown {+ // Check if handler is already wrapped to prevent double-wrapping+ // when both legacy and new API methods exist and one delegates to the other+ if ((originalHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__) {+ return originalHandler;+ }++ const wrappedHandler = function (this: unknown, ...handlerArgs: unknown[]): unknown {
try {
return createErrorCapturingHandler.call(this, originalHandler, methodName, handlerName, handlerArgs);
} catch (error) {
@@ -49,6 +55,11 @@
return originalHandler.apply(this, handlerArgs);
}
};
++ // Mark the handler as wrapped+ (wrappedHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__ = true;++ return wrappedHandler;
}
/**
diff --git a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts--- a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts+++ b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts@@ -178,4 +178,45 @@
}).not.toThrow();
});
});
++ describe('Double-wrapping prevention', () => {+ it('should not double-wrap handlers when both tool() and registerTool() exist and one delegates to the other', () => {+ // Create a mock server with both APIs where tool() delegates to registerTool()+ const registeredHandlers = new Map<string, any>();+ const mockServerWithBothApis = {+ registerTool: vi.fn((name: string, ...args: any[]) => {+ const handler = args[args.length - 1];+ registeredHandlers.set(name, handler);+ }),+ tool: vi.fn(function (this: any, name: string, handler: any) {+ // Simulate legacy tool() delegating to registerTool()+ return this.registerTool(name, {}, handler);+ }),+ resource: vi.fn(),+ prompt: vi.fn(),+ connect: vi.fn().mockResolvedValue(undefined),+ server: {+ setRequestHandler: vi.fn(),+ },+ };++ const wrapped = wrapMcpServerWithSentry(mockServerWithBothApis);++ // Register a handler via the legacy tool() method+ const originalHandler = vi.fn();+ wrapped.tool('test-tool', originalHandler);++ // Get the registered handler+ const registeredHandler = registeredHandlers.get('test-tool');++ // The handler should be wrapped (have the __sentry_wrapped__ marker)+ expect((registeredHandler as any).__sentry_wrapped__).toBe(true);++ // Verify that calling tool() only wraps once, not twice+ // If double-wrapped, the handler would have nested wrappers+ // We can verify this by checking that the registered handler is the wrapped version+ // and not a double-wrapped version+ expect(registeredHandler).not.toBe(originalHandler);+ });+ });
});

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment threadpackages/core/src/integrations/mcp-server/handlers.ts
Adds a new optional e2e test application that pins @modelcontextprotocol/sdk
v2 (split into @modelcontextprotocol/server + @modelcontextprotocol/node) and
exercises only the register* API (registerTool, registerResource, registerPrompt),
which is the only API supported in v2.
The app is marked as optional in CI since v2 is still in alpha.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
betegonand others added 2 commits April 6, 2026 08:49
…rrelation
Add mcp.transport assertion (NodeStreamableHTTPServerTransport) and
mcp.tool.result.content_count check to prove span correlation completes
with results end-to-end, matching the coverage level of the v1 tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getPlaywrightConfig starts 'node start-event-proxy.mjs' on port 3031
unconditionally. Without it the event proxy never starts and all
waitForTransaction calls hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit effc0c9. Configure here.

betegonand others added 4 commits April 6, 2026 09:10
Was copied from node-express but not updated. waitForTransaction keyed
on 'node-express-mcp-v2' would never match events from a proxy
advertising 'node-express', causing all tests to hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@modelcontextprotocol/server declares it as an optional peer dependency,
so pnpm doesn't install it automatically. Without it the app fails at
startup with ERR_MODULE_NOT_FOUND.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The app runs as ESM ("type": "module") because the MCP SDK v2 packages
are ESM-only. Sentry must be loaded via --import before the app module
to instrument Express correctly; importing it inside app.ts is too late.
Extracts Sentry.init() into instrument.mjs and starts the app with
node --import ./instrument.mjs dist/app.js, matching the pattern used
by tsx-express.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ess-mcp-v2
Without .npmrc, pnpm resolves @sentry/* from the public registry instead of
Verdaccio, so the published validateMcpServerInstance (v1-only) rejects the
v2 McpServer and no instrumentation is applied — causing the 30s timeout.
Also fix the mcp.transport assertion: NodeStreamableHTTPServerTransport
proxies onmessage to its inner WebStandardStreamableHTTPServerTransport,
so constructor.name in the wrapper is the inner class. Use the broader
/StreamableHTTPServerTransport/ regex (matching the v1 test pattern).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
s1gr1d added a commit that referenced this pull request Apr 7, 2026
Nuxt 5 is still in development and currently in a nightly version. The
E2E tests might fail due to changes but this should not block CI as long
as v5 is in nightly.
This adds the `optional` label to the test.
E.g. unblocking this PR:
#20071
@betegon
betegon merged commit 0f35883 into developApr 7, 2026
473 of 474 checks passed
@betegon
betegon deleted the feat/mcp-register-api-support branch April 7, 2026 11:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP Server - support new MCP server typescript SDK APIs

3 participants

@betegon@JPeer264@nicohrubec
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(core): support registerTool/registerResource/registerPrompt in MCP integration - #20071

Merged
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support
Apr 7, 2026
Merged

feat(core): support registerTool/registerResource/registerPrompt in MCP integration#20071
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support

Conversation

@betegon

Copy link
Copy Markdown
Member

The @modelcontextprotocol/sdk introduced registerTool, registerResource, and registerPrompt as a new API in 1.x, and in 2.x these are the only methods available — the old tool/resource/prompt names are gone.

Before this change, servers using the new API would silently get no instrumentation: validateMcpServerInstance would reject them (it only checked for the old names), so wrapMcpServerWithSentry would return the unwrapped instance. The cloudflare-mcp e2e app already used registerTool and was affected by this.

Changes

  • MCPServerInstance type now includes optional registerTool?, registerResource?, registerPrompt? alongside the legacy methods (also made legacy ones optional with @deprecated tags since 2.x removed them)
  • validateMcpServerInstance now accepts instances with either tool+resource+prompt+connect or registerTool+registerResource+registerPrompt+connect
  • wrapAllMCPHandlers conditionally wraps whichever set of methods exists on the instance
  • captureHandlerError maps registerTooltool_execution, registerResourceresource_execution, registerPromptprompt_execution
  • Unit tests added for validation and wrapping of the new method names
  • registerTool handlers added to the node-express, node-express-v5, and tsx-express e2e apps

The existing wrapMethodHandler logic (intercepts the last argument as the callback) works identically for both old and new signatures, so no changes were needed there.

  • Tests added
  • Lints and test suite passes

Closes#16666

betegonand others added 2 commits April 1, 2026 12:36
…CP integration
The @modelcontextprotocol/sdk introduced register* methods alongside the
legacy tool/resource/prompt API in 1.x, and made them the only option in 2.x.
- MCPServerInstance now accepts both old and new method names
- validateMcpServerInstance accepts servers with either API set
- wrapAllMCPHandlers instruments whichever methods are present
- captureHandlerError maps register* names to the same error categories
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
- Add createMockMcpServerWithRegisterApi() to test utilities
- Test validation accepts register*-only servers and rejects invalid ones
- Test that registerTool/registerResource/registerPrompt get wrapped
- Add registerTool handler to node-express, node-express-v5, tsx-express e2e apps
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Core

  • Support registerTool/registerResource/registerPrompt in MCP integration by betegon in #20071
  • Support embeddings in langchain by nicohrubec in #20017

Deps

  • Bump lodash.template from 4.5.0 to 4.18.1 by dependabot in #20085
  • Bump @xmldom/xmldom from 0.8.3 to 0.8.12 by dependabot in #20066

Other

  • (core, node) Portable Express integration by isaacs in #19928
  • (deno) Add denoRuntimeMetricsIntegration by chargome in #20023
  • (node, bun) Enforce minimum collection interval in runtime metrics integrations by chargome in #20068

Bug Fixes 🐛

  • (aws-serverless) Add timeout to _endSpan forceFlush to prevent Lambda hanging by logaretm in #20064
  • (cloudflare) Ensure every request instruments functions by JPeer264 in #20044
  • (core) Set span.status to error when MCP tool returns JSON-RPC error response by betegon in #20082
  • (gatsby) Fix errorHandler signature to match bundler-plugin-core API by JPeer264 in #20048

Internal Changes 🔧

Core

  • Do not emit spans for chats.create in google-genai by nicohrubec in #19990
  • Unify .do* span ops to gen_ai.generate_content by nicohrubec in #20074
  • Simplify addResponseAttributes in openai integration by nicohrubec in #20013
  • Extract shared endStreamSpan for AI integrations by nicohrubec in #20021
  • Remove provider-specific AI span attributes in favor of gen_ai attributes in sentry conventions by nicohrubec in #20011

Deps

  • Bump mshick/add-pr-comment from dd126dd8c253650d181ad9538d8b4fa218fc31e8 to e7516d74559b5514092f5b096ed29a629a1237c6 by dependabot in #20078
  • Bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.24.1 to 2.25.2 by dependabot in #20081

Other

  • (node) Add node integration tests for Vercel ToolLoopAgent by nicohrubec in #20087
  • (nuxt) Make Nuxt 5 (nightly) E2E optional by s1gr1d in #20113
  • Update validate-pr workflow by stephanie-anderson in #20072
  • Remove unused tsconfig-template folder by mydea in #20067

🤖 This preview updates automatically when you update the PR.

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser25.65 kB+0.02%+5 B 🔺
@sentry/browser - with treeshaking flags24.14 kB+0.03%+5 B 🔺
@sentry/browser (incl. Tracing)42.16 kB+0.02%+7 B 🔺
@sentry/browser (incl. Tracing, Profiling)46.77 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay)80.94 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags70.56 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)85.66 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)97.92 kB+0.01%+5 B 🔺
@sentry/browser (incl. Feedback)42.42 kB+0.02%+6 B 🔺
@sentry/browser (incl. sendFeedback)30.31 kB+0.02%+6 B 🔺
@sentry/browser (incl. FeedbackAsync)35.3 kB+0.05%+17 B 🔺
@sentry/browser (incl. Metrics)26.96 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)27.11 kB+0.03%+7 B 🔺
@sentry/browser (incl. Metrics & Logs)27.78 kB+0.03%+7 B 🔺
@sentry/react27.41 kB+0.03%+6 B 🔺
@sentry/react (incl. Tracing)44.48 kB+0.02%+5 B 🔺
@sentry/vue30.08 kB+0.02%+5 B 🔺
@sentry/vue (incl. Tracing)44.05 kB+0.02%+8 B 🔺
@sentry/svelte25.67 kB+0.02%+5 B 🔺
CDN Bundle28.33 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing)43.13 kB+0.06%+25 B 🔺
CDN Bundle (incl. Logs, Metrics)29.7 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)44.17 kB+0.05%+19 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics)68.5 kB+0.03%+17 B 🔺
CDN Bundle (incl. Tracing, Replay)80.02 kB+0.03%+19 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)81.06 kB+0.03%+18 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)85.56 kB+0.02%+15 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)86.59 kB+0.02%+16 B 🔺
CDN Bundle - uncompressed82.72 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing) - uncompressed127.86 kB+0.05%+54 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed86.86 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.27 kB+0.05%+54 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.84 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed244.74 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.13 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed257.65 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.04 kB+0.03%+54 B 🔺
@sentry/nextjs (client)46.9 kB+0.02%+7 B 🔺
@sentry/sveltekit (client)42.62 kB+0.02%+7 B 🔺
@sentry/node-core55.77 kB+0.04%+17 B 🔺
@sentry/node172.41 kB-0.2%-337 B 🔽
@sentry/node - without tracing96.05 kB+0.05%+40 B 🔺
@sentry/aws-serverless112.85 kB+0.07%+78 B 🔺

View base workflow run

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.
⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline9,220-8,855+4%
GET With Sentry1,76919%1,740+2%
GET With Sentry (error only)5,99365%6,020-0%
POST Baseline1,196-1,163+3%
POST With Sentry61652%588+5%
POST With Sentry (error only)1,07590%1,035+4%
MYSQL Baseline3,275-3,155+4%
MYSQL With Sentry48915%440+11%
MYSQL With Sentry (error only)2,60279%2,567+1%

View base workflow run

@betegon
betegon marked this pull request as ready for review April 1, 2026 12:31
betegonand others added 2 commits April 1, 2026 14:48
Call echo-register tool in SSE transport tests across node-express,
node-express-v5, and tsx-express, verifying that tools/call transactions
are recorded for handlers registered via registerTool.
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegonbetegon self-assigned this Apr 1, 2026
…ror response
When a tool handler threw an error, completeSpanWithResults() was ending
the span without setting its status to error. This caused all MCP tool
spans to appear with span.status=ok in Sentry, breaking the failure_rate()
metric in the MCP insights dashboard.
The fix passes hasError=true to completeSpanWithResults() when the
outgoing JSON-RPC response contains an error object, setting the span
status to internal_error directly on the stored span (bypassing
getActiveSpan() which doesn't return the right span at send() time).
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegon
betegonforce-pushed the feat/mcp-register-api-support branch from 68356d4 to 8984639CompareApril 1, 2026 19:37

@JPeer264JPeer264 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition. It would be nice if there would be an additional e2e test that has the v2 of the @modelcontextprotocol/sdk as well. Right now we only test against v1

@nicohrubecnicohrubec left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Potential double-wrapping when both API sets exist
    • Added sentry_wrapped marker to prevent handlers from being wrapped twice when legacy API delegates to new API.

Create PR

Or push these changes by commenting:

@cursor push 826164bfd2
Preview (826164bfd2)
diff --git a/packages/core/src/integrations/mcp-server/handlers.ts b/packages/core/src/integrations/mcp-server/handlers.ts--- a/packages/core/src/integrations/mcp-server/handlers.ts+++ b/packages/core/src/integrations/mcp-server/handlers.ts@@ -41,7 +41,13 @@
* @returns Wrapped handler function
*/
function createWrappedHandler(originalHandler: MCPHandler, methodName: keyof MCPServerInstance, handlerName: string) {
- return function (this: unknown, ...handlerArgs: unknown[]): unknown {+ // Check if handler is already wrapped to prevent double-wrapping+ // when both legacy and new API methods exist and one delegates to the other+ if ((originalHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__) {+ return originalHandler;+ }++ const wrappedHandler = function (this: unknown, ...handlerArgs: unknown[]): unknown {
try {
return createErrorCapturingHandler.call(this, originalHandler, methodName, handlerName, handlerArgs);
} catch (error) {
@@ -49,6 +55,11 @@
return originalHandler.apply(this, handlerArgs);
}
};
++ // Mark the handler as wrapped+ (wrappedHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__ = true;++ return wrappedHandler;
}
/**
diff --git a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts--- a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts+++ b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts@@ -178,4 +178,45 @@
}).not.toThrow();
});
});
++ describe('Double-wrapping prevention', () => {+ it('should not double-wrap handlers when both tool() and registerTool() exist and one delegates to the other', () => {+ // Create a mock server with both APIs where tool() delegates to registerTool()+ const registeredHandlers = new Map<string, any>();+ const mockServerWithBothApis = {+ registerTool: vi.fn((name: string, ...args: any[]) => {+ const handler = args[args.length - 1];+ registeredHandlers.set(name, handler);+ }),+ tool: vi.fn(function (this: any, name: string, handler: any) {+ // Simulate legacy tool() delegating to registerTool()+ return this.registerTool(name, {}, handler);+ }),+ resource: vi.fn(),+ prompt: vi.fn(),+ connect: vi.fn().mockResolvedValue(undefined),+ server: {+ setRequestHandler: vi.fn(),+ },+ };++ const wrapped = wrapMcpServerWithSentry(mockServerWithBothApis);++ // Register a handler via the legacy tool() method+ const originalHandler = vi.fn();+ wrapped.tool('test-tool', originalHandler);++ // Get the registered handler+ const registeredHandler = registeredHandlers.get('test-tool');++ // The handler should be wrapped (have the __sentry_wrapped__ marker)+ expect((registeredHandler as any).__sentry_wrapped__).toBe(true);++ // Verify that calling tool() only wraps once, not twice+ // If double-wrapped, the handler would have nested wrappers+ // We can verify this by checking that the registered handler is the wrapped version+ // and not a double-wrapped version+ expect(registeredHandler).not.toBe(originalHandler);+ });+ });
});

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment threadpackages/core/src/integrations/mcp-server/handlers.ts
Adds a new optional e2e test application that pins @modelcontextprotocol/sdk
v2 (split into @modelcontextprotocol/server + @modelcontextprotocol/node) and
exercises only the register* API (registerTool, registerResource, registerPrompt),
which is the only API supported in v2.
The app is marked as optional in CI since v2 is still in alpha.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
betegonand others added 2 commits April 6, 2026 08:49
…rrelation
Add mcp.transport assertion (NodeStreamableHTTPServerTransport) and
mcp.tool.result.content_count check to prove span correlation completes
with results end-to-end, matching the coverage level of the v1 tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getPlaywrightConfig starts 'node start-event-proxy.mjs' on port 3031
unconditionally. Without it the event proxy never starts and all
waitForTransaction calls hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit effc0c9. Configure here.

betegonand others added 4 commits April 6, 2026 09:10
Was copied from node-express but not updated. waitForTransaction keyed
on 'node-express-mcp-v2' would never match events from a proxy
advertising 'node-express', causing all tests to hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@modelcontextprotocol/server declares it as an optional peer dependency,
so pnpm doesn't install it automatically. Without it the app fails at
startup with ERR_MODULE_NOT_FOUND.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The app runs as ESM ("type": "module") because the MCP SDK v2 packages
are ESM-only. Sentry must be loaded via --import before the app module
to instrument Express correctly; importing it inside app.ts is too late.
Extracts Sentry.init() into instrument.mjs and starts the app with
node --import ./instrument.mjs dist/app.js, matching the pattern used
by tsx-express.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ess-mcp-v2
Without .npmrc, pnpm resolves @sentry/* from the public registry instead of
Verdaccio, so the published validateMcpServerInstance (v1-only) rejects the
v2 McpServer and no instrumentation is applied — causing the 30s timeout.
Also fix the mcp.transport assertion: NodeStreamableHTTPServerTransport
proxies onmessage to its inner WebStandardStreamableHTTPServerTransport,
so constructor.name in the wrapper is the inner class. Use the broader
/StreamableHTTPServerTransport/ regex (matching the v1 test pattern).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
s1gr1d added a commit that referenced this pull request Apr 7, 2026
Nuxt 5 is still in development and currently in a nightly version. The
E2E tests might fail due to changes but this should not block CI as long
as v5 is in nightly.
This adds the `optional` label to the test.
E.g. unblocking this PR:
#20071
@betegon
betegon merged commit 0f35883 into developApr 7, 2026
473 of 474 checks passed
@betegon
betegon deleted the feat/mcp-register-api-support branch April 7, 2026 11:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP Server - support new MCP server typescript SDK APIs

3 participants

@betegon@JPeer264@nicohrubec
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(core): support registerTool/registerResource/registerPrompt in MCP integration - #20071

Merged
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support
Apr 7, 2026
Merged

feat(core): support registerTool/registerResource/registerPrompt in MCP integration#20071
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support

Conversation

@betegon

Copy link
Copy Markdown
Member

The @modelcontextprotocol/sdk introduced registerTool, registerResource, and registerPrompt as a new API in 1.x, and in 2.x these are the only methods available — the old tool/resource/prompt names are gone.

Before this change, servers using the new API would silently get no instrumentation: validateMcpServerInstance would reject them (it only checked for the old names), so wrapMcpServerWithSentry would return the unwrapped instance. The cloudflare-mcp e2e app already used registerTool and was affected by this.

Changes

  • MCPServerInstance type now includes optional registerTool?, registerResource?, registerPrompt? alongside the legacy methods (also made legacy ones optional with @deprecated tags since 2.x removed them)
  • validateMcpServerInstance now accepts instances with either tool+resource+prompt+connect or registerTool+registerResource+registerPrompt+connect
  • wrapAllMCPHandlers conditionally wraps whichever set of methods exists on the instance
  • captureHandlerError maps registerTooltool_execution, registerResourceresource_execution, registerPromptprompt_execution
  • Unit tests added for validation and wrapping of the new method names
  • registerTool handlers added to the node-express, node-express-v5, and tsx-express e2e apps

The existing wrapMethodHandler logic (intercepts the last argument as the callback) works identically for both old and new signatures, so no changes were needed there.

  • Tests added
  • Lints and test suite passes

Closes#16666

betegonand others added 2 commits April 1, 2026 12:36
…CP integration
The @modelcontextprotocol/sdk introduced register* methods alongside the
legacy tool/resource/prompt API in 1.x, and made them the only option in 2.x.
- MCPServerInstance now accepts both old and new method names
- validateMcpServerInstance accepts servers with either API set
- wrapAllMCPHandlers instruments whichever methods are present
- captureHandlerError maps register* names to the same error categories
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
- Add createMockMcpServerWithRegisterApi() to test utilities
- Test validation accepts register*-only servers and rejects invalid ones
- Test that registerTool/registerResource/registerPrompt get wrapped
- Add registerTool handler to node-express, node-express-v5, tsx-express e2e apps
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Core

  • Support registerTool/registerResource/registerPrompt in MCP integration by betegon in #20071
  • Support embeddings in langchain by nicohrubec in #20017

Deps

  • Bump lodash.template from 4.5.0 to 4.18.1 by dependabot in #20085
  • Bump @xmldom/xmldom from 0.8.3 to 0.8.12 by dependabot in #20066

Other

  • (core, node) Portable Express integration by isaacs in #19928
  • (deno) Add denoRuntimeMetricsIntegration by chargome in #20023
  • (node, bun) Enforce minimum collection interval in runtime metrics integrations by chargome in #20068

Bug Fixes 🐛

  • (aws-serverless) Add timeout to _endSpan forceFlush to prevent Lambda hanging by logaretm in #20064
  • (cloudflare) Ensure every request instruments functions by JPeer264 in #20044
  • (core) Set span.status to error when MCP tool returns JSON-RPC error response by betegon in #20082
  • (gatsby) Fix errorHandler signature to match bundler-plugin-core API by JPeer264 in #20048

Internal Changes 🔧

Core

  • Do not emit spans for chats.create in google-genai by nicohrubec in #19990
  • Unify .do* span ops to gen_ai.generate_content by nicohrubec in #20074
  • Simplify addResponseAttributes in openai integration by nicohrubec in #20013
  • Extract shared endStreamSpan for AI integrations by nicohrubec in #20021
  • Remove provider-specific AI span attributes in favor of gen_ai attributes in sentry conventions by nicohrubec in #20011

Deps

  • Bump mshick/add-pr-comment from dd126dd8c253650d181ad9538d8b4fa218fc31e8 to e7516d74559b5514092f5b096ed29a629a1237c6 by dependabot in #20078
  • Bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.24.1 to 2.25.2 by dependabot in #20081

Other

  • (node) Add node integration tests for Vercel ToolLoopAgent by nicohrubec in #20087
  • (nuxt) Make Nuxt 5 (nightly) E2E optional by s1gr1d in #20113
  • Update validate-pr workflow by stephanie-anderson in #20072
  • Remove unused tsconfig-template folder by mydea in #20067

🤖 This preview updates automatically when you update the PR.

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser25.65 kB+0.02%+5 B 🔺
@sentry/browser - with treeshaking flags24.14 kB+0.03%+5 B 🔺
@sentry/browser (incl. Tracing)42.16 kB+0.02%+7 B 🔺
@sentry/browser (incl. Tracing, Profiling)46.77 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay)80.94 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags70.56 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)85.66 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)97.92 kB+0.01%+5 B 🔺
@sentry/browser (incl. Feedback)42.42 kB+0.02%+6 B 🔺
@sentry/browser (incl. sendFeedback)30.31 kB+0.02%+6 B 🔺
@sentry/browser (incl. FeedbackAsync)35.3 kB+0.05%+17 B 🔺
@sentry/browser (incl. Metrics)26.96 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)27.11 kB+0.03%+7 B 🔺
@sentry/browser (incl. Metrics & Logs)27.78 kB+0.03%+7 B 🔺
@sentry/react27.41 kB+0.03%+6 B 🔺
@sentry/react (incl. Tracing)44.48 kB+0.02%+5 B 🔺
@sentry/vue30.08 kB+0.02%+5 B 🔺
@sentry/vue (incl. Tracing)44.05 kB+0.02%+8 B 🔺
@sentry/svelte25.67 kB+0.02%+5 B 🔺
CDN Bundle28.33 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing)43.13 kB+0.06%+25 B 🔺
CDN Bundle (incl. Logs, Metrics)29.7 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)44.17 kB+0.05%+19 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics)68.5 kB+0.03%+17 B 🔺
CDN Bundle (incl. Tracing, Replay)80.02 kB+0.03%+19 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)81.06 kB+0.03%+18 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)85.56 kB+0.02%+15 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)86.59 kB+0.02%+16 B 🔺
CDN Bundle - uncompressed82.72 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing) - uncompressed127.86 kB+0.05%+54 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed86.86 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.27 kB+0.05%+54 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.84 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed244.74 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.13 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed257.65 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.04 kB+0.03%+54 B 🔺
@sentry/nextjs (client)46.9 kB+0.02%+7 B 🔺
@sentry/sveltekit (client)42.62 kB+0.02%+7 B 🔺
@sentry/node-core55.77 kB+0.04%+17 B 🔺
@sentry/node172.41 kB-0.2%-337 B 🔽
@sentry/node - without tracing96.05 kB+0.05%+40 B 🔺
@sentry/aws-serverless112.85 kB+0.07%+78 B 🔺

View base workflow run

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.
⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline9,220-8,855+4%
GET With Sentry1,76919%1,740+2%
GET With Sentry (error only)5,99365%6,020-0%
POST Baseline1,196-1,163+3%
POST With Sentry61652%588+5%
POST With Sentry (error only)1,07590%1,035+4%
MYSQL Baseline3,275-3,155+4%
MYSQL With Sentry48915%440+11%
MYSQL With Sentry (error only)2,60279%2,567+1%

View base workflow run

@betegon
betegon marked this pull request as ready for review April 1, 2026 12:31
betegonand others added 2 commits April 1, 2026 14:48
Call echo-register tool in SSE transport tests across node-express,
node-express-v5, and tsx-express, verifying that tools/call transactions
are recorded for handlers registered via registerTool.
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegonbetegon self-assigned this Apr 1, 2026
…ror response
When a tool handler threw an error, completeSpanWithResults() was ending
the span without setting its status to error. This caused all MCP tool
spans to appear with span.status=ok in Sentry, breaking the failure_rate()
metric in the MCP insights dashboard.
The fix passes hasError=true to completeSpanWithResults() when the
outgoing JSON-RPC response contains an error object, setting the span
status to internal_error directly on the stored span (bypassing
getActiveSpan() which doesn't return the right span at send() time).
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegon
betegonforce-pushed the feat/mcp-register-api-support branch from 68356d4 to 8984639CompareApril 1, 2026 19:37

@JPeer264JPeer264 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition. It would be nice if there would be an additional e2e test that has the v2 of the @modelcontextprotocol/sdk as well. Right now we only test against v1

@nicohrubecnicohrubec left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Potential double-wrapping when both API sets exist
    • Added sentry_wrapped marker to prevent handlers from being wrapped twice when legacy API delegates to new API.

Create PR

Or push these changes by commenting:

@cursor push 826164bfd2
Preview (826164bfd2)
diff --git a/packages/core/src/integrations/mcp-server/handlers.ts b/packages/core/src/integrations/mcp-server/handlers.ts--- a/packages/core/src/integrations/mcp-server/handlers.ts+++ b/packages/core/src/integrations/mcp-server/handlers.ts@@ -41,7 +41,13 @@
* @returns Wrapped handler function
*/
function createWrappedHandler(originalHandler: MCPHandler, methodName: keyof MCPServerInstance, handlerName: string) {
- return function (this: unknown, ...handlerArgs: unknown[]): unknown {+ // Check if handler is already wrapped to prevent double-wrapping+ // when both legacy and new API methods exist and one delegates to the other+ if ((originalHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__) {+ return originalHandler;+ }++ const wrappedHandler = function (this: unknown, ...handlerArgs: unknown[]): unknown {
try {
return createErrorCapturingHandler.call(this, originalHandler, methodName, handlerName, handlerArgs);
} catch (error) {
@@ -49,6 +55,11 @@
return originalHandler.apply(this, handlerArgs);
}
};
++ // Mark the handler as wrapped+ (wrappedHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__ = true;++ return wrappedHandler;
}
/**
diff --git a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts--- a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts+++ b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts@@ -178,4 +178,45 @@
}).not.toThrow();
});
});
++ describe('Double-wrapping prevention', () => {+ it('should not double-wrap handlers when both tool() and registerTool() exist and one delegates to the other', () => {+ // Create a mock server with both APIs where tool() delegates to registerTool()+ const registeredHandlers = new Map<string, any>();+ const mockServerWithBothApis = {+ registerTool: vi.fn((name: string, ...args: any[]) => {+ const handler = args[args.length - 1];+ registeredHandlers.set(name, handler);+ }),+ tool: vi.fn(function (this: any, name: string, handler: any) {+ // Simulate legacy tool() delegating to registerTool()+ return this.registerTool(name, {}, handler);+ }),+ resource: vi.fn(),+ prompt: vi.fn(),+ connect: vi.fn().mockResolvedValue(undefined),+ server: {+ setRequestHandler: vi.fn(),+ },+ };++ const wrapped = wrapMcpServerWithSentry(mockServerWithBothApis);++ // Register a handler via the legacy tool() method+ const originalHandler = vi.fn();+ wrapped.tool('test-tool', originalHandler);++ // Get the registered handler+ const registeredHandler = registeredHandlers.get('test-tool');++ // The handler should be wrapped (have the __sentry_wrapped__ marker)+ expect((registeredHandler as any).__sentry_wrapped__).toBe(true);++ // Verify that calling tool() only wraps once, not twice+ // If double-wrapped, the handler would have nested wrappers+ // We can verify this by checking that the registered handler is the wrapped version+ // and not a double-wrapped version+ expect(registeredHandler).not.toBe(originalHandler);+ });+ });
});

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment threadpackages/core/src/integrations/mcp-server/handlers.ts
Adds a new optional e2e test application that pins @modelcontextprotocol/sdk
v2 (split into @modelcontextprotocol/server + @modelcontextprotocol/node) and
exercises only the register* API (registerTool, registerResource, registerPrompt),
which is the only API supported in v2.
The app is marked as optional in CI since v2 is still in alpha.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
betegonand others added 2 commits April 6, 2026 08:49
…rrelation
Add mcp.transport assertion (NodeStreamableHTTPServerTransport) and
mcp.tool.result.content_count check to prove span correlation completes
with results end-to-end, matching the coverage level of the v1 tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getPlaywrightConfig starts 'node start-event-proxy.mjs' on port 3031
unconditionally. Without it the event proxy never starts and all
waitForTransaction calls hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit effc0c9. Configure here.

betegonand others added 4 commits April 6, 2026 09:10
Was copied from node-express but not updated. waitForTransaction keyed
on 'node-express-mcp-v2' would never match events from a proxy
advertising 'node-express', causing all tests to hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@modelcontextprotocol/server declares it as an optional peer dependency,
so pnpm doesn't install it automatically. Without it the app fails at
startup with ERR_MODULE_NOT_FOUND.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The app runs as ESM ("type": "module") because the MCP SDK v2 packages
are ESM-only. Sentry must be loaded via --import before the app module
to instrument Express correctly; importing it inside app.ts is too late.
Extracts Sentry.init() into instrument.mjs and starts the app with
node --import ./instrument.mjs dist/app.js, matching the pattern used
by tsx-express.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ess-mcp-v2
Without .npmrc, pnpm resolves @sentry/* from the public registry instead of
Verdaccio, so the published validateMcpServerInstance (v1-only) rejects the
v2 McpServer and no instrumentation is applied — causing the 30s timeout.
Also fix the mcp.transport assertion: NodeStreamableHTTPServerTransport
proxies onmessage to its inner WebStandardStreamableHTTPServerTransport,
so constructor.name in the wrapper is the inner class. Use the broader
/StreamableHTTPServerTransport/ regex (matching the v1 test pattern).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
s1gr1d added a commit that referenced this pull request Apr 7, 2026
Nuxt 5 is still in development and currently in a nightly version. The
E2E tests might fail due to changes but this should not block CI as long
as v5 is in nightly.
This adds the `optional` label to the test.
E.g. unblocking this PR:
#20071
@betegon
betegon merged commit 0f35883 into developApr 7, 2026
473 of 474 checks passed
@betegon
betegon deleted the feat/mcp-register-api-support branch April 7, 2026 11:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP Server - support new MCP server typescript SDK APIs

3 participants

@betegon@JPeer264@nicohrubec
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(core): support registerTool/registerResource/registerPrompt in MCP integration - #20071

Merged
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support
Apr 7, 2026
Merged

feat(core): support registerTool/registerResource/registerPrompt in MCP integration#20071
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support

Conversation

@betegon

Copy link
Copy Markdown
Member

The @modelcontextprotocol/sdk introduced registerTool, registerResource, and registerPrompt as a new API in 1.x, and in 2.x these are the only methods available — the old tool/resource/prompt names are gone.

Before this change, servers using the new API would silently get no instrumentation: validateMcpServerInstance would reject them (it only checked for the old names), so wrapMcpServerWithSentry would return the unwrapped instance. The cloudflare-mcp e2e app already used registerTool and was affected by this.

Changes

  • MCPServerInstance type now includes optional registerTool?, registerResource?, registerPrompt? alongside the legacy methods (also made legacy ones optional with @deprecated tags since 2.x removed them)
  • validateMcpServerInstance now accepts instances with either tool+resource+prompt+connect or registerTool+registerResource+registerPrompt+connect
  • wrapAllMCPHandlers conditionally wraps whichever set of methods exists on the instance
  • captureHandlerError maps registerTooltool_execution, registerResourceresource_execution, registerPromptprompt_execution
  • Unit tests added for validation and wrapping of the new method names
  • registerTool handlers added to the node-express, node-express-v5, and tsx-express e2e apps

The existing wrapMethodHandler logic (intercepts the last argument as the callback) works identically for both old and new signatures, so no changes were needed there.

  • Tests added
  • Lints and test suite passes

Closes#16666

betegonand others added 2 commits April 1, 2026 12:36
…CP integration
The @modelcontextprotocol/sdk introduced register* methods alongside the
legacy tool/resource/prompt API in 1.x, and made them the only option in 2.x.
- MCPServerInstance now accepts both old and new method names
- validateMcpServerInstance accepts servers with either API set
- wrapAllMCPHandlers instruments whichever methods are present
- captureHandlerError maps register* names to the same error categories
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
- Add createMockMcpServerWithRegisterApi() to test utilities
- Test validation accepts register*-only servers and rejects invalid ones
- Test that registerTool/registerResource/registerPrompt get wrapped
- Add registerTool handler to node-express, node-express-v5, tsx-express e2e apps
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Core

  • Support registerTool/registerResource/registerPrompt in MCP integration by betegon in #20071
  • Support embeddings in langchain by nicohrubec in #20017

Deps

  • Bump lodash.template from 4.5.0 to 4.18.1 by dependabot in #20085
  • Bump @xmldom/xmldom from 0.8.3 to 0.8.12 by dependabot in #20066

Other

  • (core, node) Portable Express integration by isaacs in #19928
  • (deno) Add denoRuntimeMetricsIntegration by chargome in #20023
  • (node, bun) Enforce minimum collection interval in runtime metrics integrations by chargome in #20068

Bug Fixes 🐛

  • (aws-serverless) Add timeout to _endSpan forceFlush to prevent Lambda hanging by logaretm in #20064
  • (cloudflare) Ensure every request instruments functions by JPeer264 in #20044
  • (core) Set span.status to error when MCP tool returns JSON-RPC error response by betegon in #20082
  • (gatsby) Fix errorHandler signature to match bundler-plugin-core API by JPeer264 in #20048

Internal Changes 🔧

Core

  • Do not emit spans for chats.create in google-genai by nicohrubec in #19990
  • Unify .do* span ops to gen_ai.generate_content by nicohrubec in #20074
  • Simplify addResponseAttributes in openai integration by nicohrubec in #20013
  • Extract shared endStreamSpan for AI integrations by nicohrubec in #20021
  • Remove provider-specific AI span attributes in favor of gen_ai attributes in sentry conventions by nicohrubec in #20011

Deps

  • Bump mshick/add-pr-comment from dd126dd8c253650d181ad9538d8b4fa218fc31e8 to e7516d74559b5514092f5b096ed29a629a1237c6 by dependabot in #20078
  • Bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.24.1 to 2.25.2 by dependabot in #20081

Other

  • (node) Add node integration tests for Vercel ToolLoopAgent by nicohrubec in #20087
  • (nuxt) Make Nuxt 5 (nightly) E2E optional by s1gr1d in #20113
  • Update validate-pr workflow by stephanie-anderson in #20072
  • Remove unused tsconfig-template folder by mydea in #20067

🤖 This preview updates automatically when you update the PR.

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser25.65 kB+0.02%+5 B 🔺
@sentry/browser - with treeshaking flags24.14 kB+0.03%+5 B 🔺
@sentry/browser (incl. Tracing)42.16 kB+0.02%+7 B 🔺
@sentry/browser (incl. Tracing, Profiling)46.77 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay)80.94 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags70.56 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)85.66 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)97.92 kB+0.01%+5 B 🔺
@sentry/browser (incl. Feedback)42.42 kB+0.02%+6 B 🔺
@sentry/browser (incl. sendFeedback)30.31 kB+0.02%+6 B 🔺
@sentry/browser (incl. FeedbackAsync)35.3 kB+0.05%+17 B 🔺
@sentry/browser (incl. Metrics)26.96 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)27.11 kB+0.03%+7 B 🔺
@sentry/browser (incl. Metrics & Logs)27.78 kB+0.03%+7 B 🔺
@sentry/react27.41 kB+0.03%+6 B 🔺
@sentry/react (incl. Tracing)44.48 kB+0.02%+5 B 🔺
@sentry/vue30.08 kB+0.02%+5 B 🔺
@sentry/vue (incl. Tracing)44.05 kB+0.02%+8 B 🔺
@sentry/svelte25.67 kB+0.02%+5 B 🔺
CDN Bundle28.33 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing)43.13 kB+0.06%+25 B 🔺
CDN Bundle (incl. Logs, Metrics)29.7 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)44.17 kB+0.05%+19 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics)68.5 kB+0.03%+17 B 🔺
CDN Bundle (incl. Tracing, Replay)80.02 kB+0.03%+19 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)81.06 kB+0.03%+18 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)85.56 kB+0.02%+15 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)86.59 kB+0.02%+16 B 🔺
CDN Bundle - uncompressed82.72 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing) - uncompressed127.86 kB+0.05%+54 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed86.86 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.27 kB+0.05%+54 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.84 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed244.74 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.13 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed257.65 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.04 kB+0.03%+54 B 🔺
@sentry/nextjs (client)46.9 kB+0.02%+7 B 🔺
@sentry/sveltekit (client)42.62 kB+0.02%+7 B 🔺
@sentry/node-core55.77 kB+0.04%+17 B 🔺
@sentry/node172.41 kB-0.2%-337 B 🔽
@sentry/node - without tracing96.05 kB+0.05%+40 B 🔺
@sentry/aws-serverless112.85 kB+0.07%+78 B 🔺

View base workflow run

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.
⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline9,220-8,855+4%
GET With Sentry1,76919%1,740+2%
GET With Sentry (error only)5,99365%6,020-0%
POST Baseline1,196-1,163+3%
POST With Sentry61652%588+5%
POST With Sentry (error only)1,07590%1,035+4%
MYSQL Baseline3,275-3,155+4%
MYSQL With Sentry48915%440+11%
MYSQL With Sentry (error only)2,60279%2,567+1%

View base workflow run

@betegon
betegon marked this pull request as ready for review April 1, 2026 12:31
betegonand others added 2 commits April 1, 2026 14:48
Call echo-register tool in SSE transport tests across node-express,
node-express-v5, and tsx-express, verifying that tools/call transactions
are recorded for handlers registered via registerTool.
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegonbetegon self-assigned this Apr 1, 2026
…ror response
When a tool handler threw an error, completeSpanWithResults() was ending
the span without setting its status to error. This caused all MCP tool
spans to appear with span.status=ok in Sentry, breaking the failure_rate()
metric in the MCP insights dashboard.
The fix passes hasError=true to completeSpanWithResults() when the
outgoing JSON-RPC response contains an error object, setting the span
status to internal_error directly on the stored span (bypassing
getActiveSpan() which doesn't return the right span at send() time).
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegon
betegonforce-pushed the feat/mcp-register-api-support branch from 68356d4 to 8984639CompareApril 1, 2026 19:37

@JPeer264JPeer264 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition. It would be nice if there would be an additional e2e test that has the v2 of the @modelcontextprotocol/sdk as well. Right now we only test against v1

@nicohrubecnicohrubec left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Potential double-wrapping when both API sets exist
    • Added sentry_wrapped marker to prevent handlers from being wrapped twice when legacy API delegates to new API.

Create PR

Or push these changes by commenting:

@cursor push 826164bfd2
Preview (826164bfd2)
diff --git a/packages/core/src/integrations/mcp-server/handlers.ts b/packages/core/src/integrations/mcp-server/handlers.ts--- a/packages/core/src/integrations/mcp-server/handlers.ts+++ b/packages/core/src/integrations/mcp-server/handlers.ts@@ -41,7 +41,13 @@
* @returns Wrapped handler function
*/
function createWrappedHandler(originalHandler: MCPHandler, methodName: keyof MCPServerInstance, handlerName: string) {
- return function (this: unknown, ...handlerArgs: unknown[]): unknown {+ // Check if handler is already wrapped to prevent double-wrapping+ // when both legacy and new API methods exist and one delegates to the other+ if ((originalHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__) {+ return originalHandler;+ }++ const wrappedHandler = function (this: unknown, ...handlerArgs: unknown[]): unknown {
try {
return createErrorCapturingHandler.call(this, originalHandler, methodName, handlerName, handlerArgs);
} catch (error) {
@@ -49,6 +55,11 @@
return originalHandler.apply(this, handlerArgs);
}
};
++ // Mark the handler as wrapped+ (wrappedHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__ = true;++ return wrappedHandler;
}
/**
diff --git a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts--- a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts+++ b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts@@ -178,4 +178,45 @@
}).not.toThrow();
});
});
++ describe('Double-wrapping prevention', () => {+ it('should not double-wrap handlers when both tool() and registerTool() exist and one delegates to the other', () => {+ // Create a mock server with both APIs where tool() delegates to registerTool()+ const registeredHandlers = new Map<string, any>();+ const mockServerWithBothApis = {+ registerTool: vi.fn((name: string, ...args: any[]) => {+ const handler = args[args.length - 1];+ registeredHandlers.set(name, handler);+ }),+ tool: vi.fn(function (this: any, name: string, handler: any) {+ // Simulate legacy tool() delegating to registerTool()+ return this.registerTool(name, {}, handler);+ }),+ resource: vi.fn(),+ prompt: vi.fn(),+ connect: vi.fn().mockResolvedValue(undefined),+ server: {+ setRequestHandler: vi.fn(),+ },+ };++ const wrapped = wrapMcpServerWithSentry(mockServerWithBothApis);++ // Register a handler via the legacy tool() method+ const originalHandler = vi.fn();+ wrapped.tool('test-tool', originalHandler);++ // Get the registered handler+ const registeredHandler = registeredHandlers.get('test-tool');++ // The handler should be wrapped (have the __sentry_wrapped__ marker)+ expect((registeredHandler as any).__sentry_wrapped__).toBe(true);++ // Verify that calling tool() only wraps once, not twice+ // If double-wrapped, the handler would have nested wrappers+ // We can verify this by checking that the registered handler is the wrapped version+ // and not a double-wrapped version+ expect(registeredHandler).not.toBe(originalHandler);+ });+ });
});

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment threadpackages/core/src/integrations/mcp-server/handlers.ts
Adds a new optional e2e test application that pins @modelcontextprotocol/sdk
v2 (split into @modelcontextprotocol/server + @modelcontextprotocol/node) and
exercises only the register* API (registerTool, registerResource, registerPrompt),
which is the only API supported in v2.
The app is marked as optional in CI since v2 is still in alpha.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
betegonand others added 2 commits April 6, 2026 08:49
…rrelation
Add mcp.transport assertion (NodeStreamableHTTPServerTransport) and
mcp.tool.result.content_count check to prove span correlation completes
with results end-to-end, matching the coverage level of the v1 tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getPlaywrightConfig starts 'node start-event-proxy.mjs' on port 3031
unconditionally. Without it the event proxy never starts and all
waitForTransaction calls hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit effc0c9. Configure here.

betegonand others added 4 commits April 6, 2026 09:10
Was copied from node-express but not updated. waitForTransaction keyed
on 'node-express-mcp-v2' would never match events from a proxy
advertising 'node-express', causing all tests to hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@modelcontextprotocol/server declares it as an optional peer dependency,
so pnpm doesn't install it automatically. Without it the app fails at
startup with ERR_MODULE_NOT_FOUND.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The app runs as ESM ("type": "module") because the MCP SDK v2 packages
are ESM-only. Sentry must be loaded via --import before the app module
to instrument Express correctly; importing it inside app.ts is too late.
Extracts Sentry.init() into instrument.mjs and starts the app with
node --import ./instrument.mjs dist/app.js, matching the pattern used
by tsx-express.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ess-mcp-v2
Without .npmrc, pnpm resolves @sentry/* from the public registry instead of
Verdaccio, so the published validateMcpServerInstance (v1-only) rejects the
v2 McpServer and no instrumentation is applied — causing the 30s timeout.
Also fix the mcp.transport assertion: NodeStreamableHTTPServerTransport
proxies onmessage to its inner WebStandardStreamableHTTPServerTransport,
so constructor.name in the wrapper is the inner class. Use the broader
/StreamableHTTPServerTransport/ regex (matching the v1 test pattern).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
s1gr1d added a commit that referenced this pull request Apr 7, 2026
Nuxt 5 is still in development and currently in a nightly version. The
E2E tests might fail due to changes but this should not block CI as long
as v5 is in nightly.
This adds the `optional` label to the test.
E.g. unblocking this PR:
#20071
@betegon
betegon merged commit 0f35883 into developApr 7, 2026
473 of 474 checks passed
@betegon
betegon deleted the feat/mcp-register-api-support branch April 7, 2026 11:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP Server - support new MCP server typescript SDK APIs

3 participants

@betegon@JPeer264@nicohrubec
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(core): support registerTool/registerResource/registerPrompt in MCP integration - #20071

Merged
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support
Apr 7, 2026
Merged

feat(core): support registerTool/registerResource/registerPrompt in MCP integration#20071
betegon merged 14 commits into
developfrom
feat/mcp-register-api-support

Conversation

@betegon

Copy link
Copy Markdown
Member

The @modelcontextprotocol/sdk introduced registerTool, registerResource, and registerPrompt as a new API in 1.x, and in 2.x these are the only methods available — the old tool/resource/prompt names are gone.

Before this change, servers using the new API would silently get no instrumentation: validateMcpServerInstance would reject them (it only checked for the old names), so wrapMcpServerWithSentry would return the unwrapped instance. The cloudflare-mcp e2e app already used registerTool and was affected by this.

Changes

  • MCPServerInstance type now includes optional registerTool?, registerResource?, registerPrompt? alongside the legacy methods (also made legacy ones optional with @deprecated tags since 2.x removed them)
  • validateMcpServerInstance now accepts instances with either tool+resource+prompt+connect or registerTool+registerResource+registerPrompt+connect
  • wrapAllMCPHandlers conditionally wraps whichever set of methods exists on the instance
  • captureHandlerError maps registerTooltool_execution, registerResourceresource_execution, registerPromptprompt_execution
  • Unit tests added for validation and wrapping of the new method names
  • registerTool handlers added to the node-express, node-express-v5, and tsx-express e2e apps

The existing wrapMethodHandler logic (intercepts the last argument as the callback) works identically for both old and new signatures, so no changes were needed there.

  • Tests added
  • Lints and test suite passes

Closes#16666

betegonand others added 2 commits April 1, 2026 12:36
…CP integration
The @modelcontextprotocol/sdk introduced register* methods alongside the
legacy tool/resource/prompt API in 1.x, and made them the only option in 2.x.
- MCPServerInstance now accepts both old and new method names
- validateMcpServerInstance accepts servers with either API set
- wrapAllMCPHandlers instruments whichever methods are present
- captureHandlerError maps register* names to the same error categories
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
- Add createMockMcpServerWithRegisterApi() to test utilities
- Test validation accepts register*-only servers and rejects invalid ones
- Test that registerTool/registerResource/registerPrompt get wrapped
- Add registerTool handler to node-express, node-express-v5, tsx-express e2e apps
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Core

  • Support registerTool/registerResource/registerPrompt in MCP integration by betegon in #20071
  • Support embeddings in langchain by nicohrubec in #20017

Deps

  • Bump lodash.template from 4.5.0 to 4.18.1 by dependabot in #20085
  • Bump @xmldom/xmldom from 0.8.3 to 0.8.12 by dependabot in #20066

Other

  • (core, node) Portable Express integration by isaacs in #19928
  • (deno) Add denoRuntimeMetricsIntegration by chargome in #20023
  • (node, bun) Enforce minimum collection interval in runtime metrics integrations by chargome in #20068

Bug Fixes 🐛

  • (aws-serverless) Add timeout to _endSpan forceFlush to prevent Lambda hanging by logaretm in #20064
  • (cloudflare) Ensure every request instruments functions by JPeer264 in #20044
  • (core) Set span.status to error when MCP tool returns JSON-RPC error response by betegon in #20082
  • (gatsby) Fix errorHandler signature to match bundler-plugin-core API by JPeer264 in #20048

Internal Changes 🔧

Core

  • Do not emit spans for chats.create in google-genai by nicohrubec in #19990
  • Unify .do* span ops to gen_ai.generate_content by nicohrubec in #20074
  • Simplify addResponseAttributes in openai integration by nicohrubec in #20013
  • Extract shared endStreamSpan for AI integrations by nicohrubec in #20021
  • Remove provider-specific AI span attributes in favor of gen_ai attributes in sentry conventions by nicohrubec in #20011

Deps

  • Bump mshick/add-pr-comment from dd126dd8c253650d181ad9538d8b4fa218fc31e8 to e7516d74559b5514092f5b096ed29a629a1237c6 by dependabot in #20078
  • Bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.24.1 to 2.25.2 by dependabot in #20081

Other

  • (node) Add node integration tests for Vercel ToolLoopAgent by nicohrubec in #20087
  • (nuxt) Make Nuxt 5 (nightly) E2E optional by s1gr1d in #20113
  • Update validate-pr workflow by stephanie-anderson in #20072
  • Remove unused tsconfig-template folder by mydea in #20067

🤖 This preview updates automatically when you update the PR.

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser25.65 kB+0.02%+5 B 🔺
@sentry/browser - with treeshaking flags24.14 kB+0.03%+5 B 🔺
@sentry/browser (incl. Tracing)42.16 kB+0.02%+7 B 🔺
@sentry/browser (incl. Tracing, Profiling)46.77 kB+0.02%+9 B 🔺
@sentry/browser (incl. Tracing, Replay)80.94 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags70.56 kB+0.01%+5 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)85.66 kB+0.01%+8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)97.92 kB+0.01%+5 B 🔺
@sentry/browser (incl. Feedback)42.42 kB+0.02%+6 B 🔺
@sentry/browser (incl. sendFeedback)30.31 kB+0.02%+6 B 🔺
@sentry/browser (incl. FeedbackAsync)35.3 kB+0.05%+17 B 🔺
@sentry/browser (incl. Metrics)26.96 kB+0.03%+7 B 🔺
@sentry/browser (incl. Logs)27.11 kB+0.03%+7 B 🔺
@sentry/browser (incl. Metrics & Logs)27.78 kB+0.03%+7 B 🔺
@sentry/react27.41 kB+0.03%+6 B 🔺
@sentry/react (incl. Tracing)44.48 kB+0.02%+5 B 🔺
@sentry/vue30.08 kB+0.02%+5 B 🔺
@sentry/vue (incl. Tracing)44.05 kB+0.02%+8 B 🔺
@sentry/svelte25.67 kB+0.02%+5 B 🔺
CDN Bundle28.33 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing)43.13 kB+0.06%+25 B 🔺
CDN Bundle (incl. Logs, Metrics)29.7 kB+0.05%+14 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)44.17 kB+0.05%+19 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics)68.5 kB+0.03%+17 B 🔺
CDN Bundle (incl. Tracing, Replay)80.02 kB+0.03%+19 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)81.06 kB+0.03%+18 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)85.56 kB+0.02%+15 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)86.59 kB+0.02%+16 B 🔺
CDN Bundle - uncompressed82.72 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing) - uncompressed127.86 kB+0.05%+54 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed86.86 kB+0.07%+54 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed131.27 kB+0.05%+54 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed209.84 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed244.74 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed248.13 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed257.65 kB+0.03%+54 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed261.04 kB+0.03%+54 B 🔺
@sentry/nextjs (client)46.9 kB+0.02%+7 B 🔺
@sentry/sveltekit (client)42.62 kB+0.02%+7 B 🔺
@sentry/node-core55.77 kB+0.04%+17 B 🔺
@sentry/node172.41 kB-0.2%-337 B 🔽
@sentry/node - without tracing96.05 kB+0.05%+40 B 🔺
@sentry/aws-serverless112.85 kB+0.07%+78 B 🔺

View base workflow run

@github-actions

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.
⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

ScenarioRequests/s% of BaselinePrev. Requests/sChange %
GET Baseline9,220-8,855+4%
GET With Sentry1,76919%1,740+2%
GET With Sentry (error only)5,99365%6,020-0%
POST Baseline1,196-1,163+3%
POST With Sentry61652%588+5%
POST With Sentry (error only)1,07590%1,035+4%
MYSQL Baseline3,275-3,155+4%
MYSQL With Sentry48915%440+11%
MYSQL With Sentry (error only)2,60279%2,567+1%

View base workflow run

@betegon
betegon marked this pull request as ready for review April 1, 2026 12:31
betegonand others added 2 commits April 1, 2026 14:48
Call echo-register tool in SSE transport tests across node-express,
node-express-v5, and tsx-express, verifying that tools/call transactions
are recorded for handlers registered via registerTool.
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegonbetegon self-assigned this Apr 1, 2026
…ror response
When a tool handler threw an error, completeSpanWithResults() was ending
the span without setting its status to error. This caused all MCP tool
spans to appear with span.status=ok in Sentry, breaking the failure_rate()
metric in the MCP insights dashboard.
The fix passes hasError=true to completeSpanWithResults() when the
outgoing JSON-RPC response contains an error object, setting the span
status to internal_error directly on the stored span (bypassing
getActiveSpan() which doesn't return the right span at send() time).
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@betegon
betegonforce-pushed the feat/mcp-register-api-support branch from 68356d4 to 8984639CompareApril 1, 2026 19:37

@JPeer264JPeer264 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition. It would be nice if there would be an additional e2e test that has the v2 of the @modelcontextprotocol/sdk as well. Right now we only test against v1

@nicohrubecnicohrubec left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Potential double-wrapping when both API sets exist
    • Added sentry_wrapped marker to prevent handlers from being wrapped twice when legacy API delegates to new API.

Create PR

Or push these changes by commenting:

@cursor push 826164bfd2
Preview (826164bfd2)
diff --git a/packages/core/src/integrations/mcp-server/handlers.ts b/packages/core/src/integrations/mcp-server/handlers.ts--- a/packages/core/src/integrations/mcp-server/handlers.ts+++ b/packages/core/src/integrations/mcp-server/handlers.ts@@ -41,7 +41,13 @@
* @returns Wrapped handler function
*/
function createWrappedHandler(originalHandler: MCPHandler, methodName: keyof MCPServerInstance, handlerName: string) {
- return function (this: unknown, ...handlerArgs: unknown[]): unknown {+ // Check if handler is already wrapped to prevent double-wrapping+ // when both legacy and new API methods exist and one delegates to the other+ if ((originalHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__) {+ return originalHandler;+ }++ const wrappedHandler = function (this: unknown, ...handlerArgs: unknown[]): unknown {
try {
return createErrorCapturingHandler.call(this, originalHandler, methodName, handlerName, handlerArgs);
} catch (error) {
@@ -49,6 +55,11 @@
return originalHandler.apply(this, handlerArgs);
}
};
++ // Mark the handler as wrapped+ (wrappedHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__ = true;++ return wrappedHandler;
}
/**
diff --git a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts--- a/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts+++ b/packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts@@ -178,4 +178,45 @@
}).not.toThrow();
});
});
++ describe('Double-wrapping prevention', () => {+ it('should not double-wrap handlers when both tool() and registerTool() exist and one delegates to the other', () => {+ // Create a mock server with both APIs where tool() delegates to registerTool()+ const registeredHandlers = new Map<string, any>();+ const mockServerWithBothApis = {+ registerTool: vi.fn((name: string, ...args: any[]) => {+ const handler = args[args.length - 1];+ registeredHandlers.set(name, handler);+ }),+ tool: vi.fn(function (this: any, name: string, handler: any) {+ // Simulate legacy tool() delegating to registerTool()+ return this.registerTool(name, {}, handler);+ }),+ resource: vi.fn(),+ prompt: vi.fn(),+ connect: vi.fn().mockResolvedValue(undefined),+ server: {+ setRequestHandler: vi.fn(),+ },+ };++ const wrapped = wrapMcpServerWithSentry(mockServerWithBothApis);++ // Register a handler via the legacy tool() method+ const originalHandler = vi.fn();+ wrapped.tool('test-tool', originalHandler);++ // Get the registered handler+ const registeredHandler = registeredHandlers.get('test-tool');++ // The handler should be wrapped (have the __sentry_wrapped__ marker)+ expect((registeredHandler as any).__sentry_wrapped__).toBe(true);++ // Verify that calling tool() only wraps once, not twice+ // If double-wrapped, the handler would have nested wrappers+ // We can verify this by checking that the registered handler is the wrapped version+ // and not a double-wrapped version+ expect(registeredHandler).not.toBe(originalHandler);+ });+ });
});

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment threadpackages/core/src/integrations/mcp-server/handlers.ts
Adds a new optional e2e test application that pins @modelcontextprotocol/sdk
v2 (split into @modelcontextprotocol/server + @modelcontextprotocol/node) and
exercises only the register* API (registerTool, registerResource, registerPrompt),
which is the only API supported in v2.
The app is marked as optional in CI since v2 is still in alpha.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
betegonand others added 2 commits April 6, 2026 08:49
…rrelation
Add mcp.transport assertion (NodeStreamableHTTPServerTransport) and
mcp.tool.result.content_count check to prove span correlation completes
with results end-to-end, matching the coverage level of the v1 tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getPlaywrightConfig starts 'node start-event-proxy.mjs' on port 3031
unconditionally. Without it the event proxy never starts and all
waitForTransaction calls hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit effc0c9. Configure here.

betegonand others added 4 commits April 6, 2026 09:10
Was copied from node-express but not updated. waitForTransaction keyed
on 'node-express-mcp-v2' would never match events from a proxy
advertising 'node-express', causing all tests to hang.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@modelcontextprotocol/server declares it as an optional peer dependency,
so pnpm doesn't install it automatically. Without it the app fails at
startup with ERR_MODULE_NOT_FOUND.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The app runs as ESM ("type": "module") because the MCP SDK v2 packages
are ESM-only. Sentry must be loaded via --import before the app module
to instrument Express correctly; importing it inside app.ts is too late.
Extracts Sentry.init() into instrument.mjs and starts the app with
node --import ./instrument.mjs dist/app.js, matching the pattern used
by tsx-express.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ess-mcp-v2
Without .npmrc, pnpm resolves @sentry/* from the public registry instead of
Verdaccio, so the published validateMcpServerInstance (v1-only) rejects the
v2 McpServer and no instrumentation is applied — causing the 30s timeout.
Also fix the mcp.transport assertion: NodeStreamableHTTPServerTransport
proxies onmessage to its inner WebStandardStreamableHTTPServerTransport,
so constructor.name in the wrapper is the inner class. Use the broader
/StreamableHTTPServerTransport/ regex (matching the v1 test pattern).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
s1gr1d added a commit that referenced this pull request Apr 7, 2026
Nuxt 5 is still in development and currently in a nightly version. The
E2E tests might fail due to changes but this should not block CI as long
as v5 is in nightly.
This adds the `optional` label to the test.
E.g. unblocking this PR:
#20071
@betegon
betegon merged commit 0f35883 into developApr 7, 2026
473 of 474 checks passed
@betegon
betegon deleted the feat/mcp-register-api-support branch April 7, 2026 11:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP Server - support new MCP server typescript SDK APIs

3 participants

@betegon@JPeer264@nicohrubec