From c272982f9df7f8da39896c3f0c22b70e9263bf43 Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Mon, 24 Nov 2025 14:25:12 -0500 Subject: [PATCH] fix(mcp): Use mutable search in mcp link generation This uses the MutableSearch class when generating links in the mcp insights pages. It handles quoting/escaping automatically which is necessary because otherwise the generated query may be interpreted incorrectly. --- .../pages/mcp/components/mcpOverviewTable.tsx | 17 ++++++++++++++--- .../pages/mcp/components/mcpPromptsTable.tsx | 18 +++++++++++++++--- .../pages/mcp/components/mcpResourcesTable.tsx | 18 +++++++++++++++--- .../pages/mcp/components/mcpToolsTable.tsx | 18 +++++++++++++++--- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/static/app/views/insights/pages/mcp/components/mcpOverviewTable.tsx b/static/app/views/insights/pages/mcp/components/mcpOverviewTable.tsx index dcd3054c4e8953..5f97568460c444 100644 --- a/static/app/views/insights/pages/mcp/components/mcpOverviewTable.tsx +++ b/static/app/views/insights/pages/mcp/components/mcpOverviewTable.tsx @@ -8,6 +8,7 @@ import { } from 'sentry/components/tables/gridEditable'; import {t} from 'sentry/locale'; import {trackAnalytics} from 'sentry/utils/analytics'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode'; @@ -111,19 +112,26 @@ export function McpOverviewTable() { promptName={dataRow[SpanFields.MCP_PROMPT_NAME]} /> ); - case 'failure_rate()': + case 'failure_rate()': { + const search = new MutableSearch(query); + search.addFilterValue(SpanFields.SPAN_STATUS, 'internal_error'); + search.addFilterValue( + SpanFields.SPAN_DESCRIPTION, + dataRow[SpanFields.SPAN_DESCRIPTION] + ); return ( ); + } case 'count()': return ; case AVG_DURATION: @@ -178,6 +186,9 @@ function SpanDescriptionCell({ fields.push('span.duration'); fields.push('timestamp'); + const search = new MutableSearch(''); + search.addFilterValue(SpanFields.SPAN_OP, 'mcp.server'); + search.addFilterValue(SpanFields.SPAN_DESCRIPTION, spanDescription); const link = getExploreUrl({ organization, selection, @@ -188,7 +199,7 @@ function SpanDescriptionCell({ yAxes: ['count(span.duration)'], }, ], - query: `span.op:mcp.server ${SpanFields.SPAN_DESCRIPTION}:"${spanDescription}"`, + query: search.formatString(), sort: `-count(span.duration)`, field: fields, }); diff --git a/static/app/views/insights/pages/mcp/components/mcpPromptsTable.tsx b/static/app/views/insights/pages/mcp/components/mcpPromptsTable.tsx index bf5e6260e567ca..28f0ec8fde8d45 100644 --- a/static/app/views/insights/pages/mcp/components/mcpPromptsTable.tsx +++ b/static/app/views/insights/pages/mcp/components/mcpPromptsTable.tsx @@ -8,6 +8,7 @@ import { } from 'sentry/components/tables/gridEditable'; import {t} from 'sentry/locale'; import {trackAnalytics} from 'sentry/utils/analytics'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode'; @@ -99,19 +100,26 @@ export function McpPromptsTable() { switch (column.key) { case SpanFields.MCP_PROMPT_NAME: return ; - case 'failure_rate()': + case 'failure_rate()': { + const search = new MutableSearch(query); + search.addFilterValue(SpanFields.SPAN_STATUS, 'internal_error'); + search.addFilterValue( + SpanFields.MCP_PROMPT_NAME, + dataRow[SpanFields.MCP_PROMPT_NAME] + ); return ( ); + } case 'count()': return ; case AVG_DURATION: @@ -145,6 +153,10 @@ function McpPromptCell({prompt}: {prompt: string}) { const organization = useOrganization(); const {selection} = usePageFilters(); + const search = new MutableSearch(''); + search.addFilterValue(SpanFields.SPAN_OP, 'mcp.server'); + search.addFilterValue(SpanFields.MCP_PROMPT_NAME, prompt); + const link = getExploreUrl({ organization, selection, @@ -162,7 +174,7 @@ function McpPromptCell({prompt}: {prompt: string}) { 'span.duration', 'timestamp', ], - query: `span.op:mcp.server ${SpanFields.MCP_PROMPT_NAME}:"${prompt}"`, + query: search.formatString(), sort: `-count(span.duration)`, }); return {prompt}; diff --git a/static/app/views/insights/pages/mcp/components/mcpResourcesTable.tsx b/static/app/views/insights/pages/mcp/components/mcpResourcesTable.tsx index 2ec2d370be84ff..1c28a7f1fa47c1 100644 --- a/static/app/views/insights/pages/mcp/components/mcpResourcesTable.tsx +++ b/static/app/views/insights/pages/mcp/components/mcpResourcesTable.tsx @@ -8,6 +8,7 @@ import { } from 'sentry/components/tables/gridEditable'; import {t} from 'sentry/locale'; import {trackAnalytics} from 'sentry/utils/analytics'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode'; @@ -99,19 +100,26 @@ export function McpResourcesTable() { switch (column.key) { case SpanFields.MCP_RESOURCE_URI: return ; - case 'failure_rate()': + case 'failure_rate()': { + const search = new MutableSearch(query); + search.addFilterValue(SpanFields.SPAN_STATUS, 'internal_error'); + search.addFilterValue( + SpanFields.MCP_RESOURCE_URI, + dataRow[SpanFields.MCP_RESOURCE_URI] + ); return ( ); + } case 'count()': return ; case AVG_DURATION: @@ -145,6 +153,10 @@ function McpResourceCell({resource}: {resource: string}) { const organization = useOrganization(); const {selection} = usePageFilters(); + const search = new MutableSearch(''); + search.addFilterValue(SpanFields.SPAN_OP, 'mcp.server'); + search.addFilterValue(SpanFields.MCP_RESOURCE_URI, resource); + const link = getExploreUrl({ organization, selection, @@ -156,7 +168,7 @@ function McpResourceCell({resource}: {resource: string}) { }, ], field: ['span.description', 'span.status', 'span.duration', 'timestamp'], - query: `span.op:mcp.server ${SpanFields.MCP_RESOURCE_URI}:"${resource}"`, + query: search.formatString(), sort: `-count(span.duration)`, }); return {resource}; diff --git a/static/app/views/insights/pages/mcp/components/mcpToolsTable.tsx b/static/app/views/insights/pages/mcp/components/mcpToolsTable.tsx index 0f99cbddcf4a5d..00f8458a3f6f8a 100644 --- a/static/app/views/insights/pages/mcp/components/mcpToolsTable.tsx +++ b/static/app/views/insights/pages/mcp/components/mcpToolsTable.tsx @@ -8,6 +8,7 @@ import { } from 'sentry/components/tables/gridEditable'; import {t} from 'sentry/locale'; import {trackAnalytics} from 'sentry/utils/analytics'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode'; @@ -99,19 +100,26 @@ export function McpToolsTable() { switch (column.key) { case SpanFields.MCP_TOOL_NAME: return ; - case 'failure_rate()': + case 'failure_rate()': { + const search = new MutableSearch(query); + search.addFilterValue(SpanFields.SPAN_STATUS, 'internal_error'); + search.addFilterValue( + SpanFields.MCP_TOOL_NAME, + dataRow[SpanFields.MCP_TOOL_NAME] + ); return ( ); + } case 'count()': return ; case AVG_DURATION: @@ -145,6 +153,10 @@ function McpToolCell({tool}: {tool: string}) { const organization = useOrganization(); const {selection} = usePageFilters(); + const search = new MutableSearch(''); + search.addFilterValue(SpanFields.SPAN_OP, 'mcp.server'); + search.addFilterValue(SpanFields.MCP_TOOL_NAME, tool); + const link = getExploreUrl({ organization, selection, @@ -155,7 +167,7 @@ function McpToolCell({tool}: {tool: string}) { yAxes: ['count(span.duration)'], }, ], - query: `span.op:mcp.server ${SpanFields.MCP_TOOL_NAME}:"${tool}"`, + query: search.formatString(), sort: `-count(span.duration)`, field: ['span.description', 'mcp.tool.result.content', 'span.duration', 'timestamp'], });