diff --git a/.changeset/tired-bobcats-tan.md b/.changeset/tired-bobcats-tan.md
new file mode 100644
index 00000000000..10eb5d0cd24
--- /dev/null
+++ b/.changeset/tired-bobcats-tan.md
@@ -0,0 +1,5 @@
+---
+"@primer/mcp": patch
+---
+
+Update component MCP tools to match on the slug of components for HTTP requests to primer.style
diff --git a/packages/mcp/src/server.test.ts b/packages/mcp/src/server.test.ts
index 39bb6a0effc..8ad20d21a04 100644
--- a/packages/mcp/src/server.test.ts
+++ b/packages/mcp/src/server.test.ts
@@ -482,6 +482,56 @@ describe('MCP server', () => {
},
)
+ it.each([
+ {
+ tool: 'get_component_examples',
+ expectedPath: '/product/components/text-input',
+ rawIdPath: '/product/components/text_input',
+ },
+ {
+ tool: 'get_component_accessibility_guidelines',
+ expectedPath: '/product/components/text-input/accessibility',
+ rawIdPath: '/product/components/text_input/accessibility',
+ },
+ ] as const)('$tool uses the TextInput slug in its URL', async ({tool, expectedPath, rawIdPath}) => {
+ const expectedUrl = `https://primer.style${expectedPath}`
+ const rawIdUrl = `https://primer.style${rawIdPath}`
+ const responseBody = 'synthetic-text-input-documentation'
+ const homepageBody = 'homepage-body-not-text-input-documentation'
+ const fetchMock = vi.mocked(fetch)
+ fetchMock.mockImplementation(async input => {
+ const url = input.toString()
+
+ if (url === expectedUrl) {
+ return responseWithFinalUrl(`${responseBody}
`, expectedUrl)
+ }
+
+ if (url === rawIdUrl) {
+ return responseWithFinalUrl(`${homepageBody}
`, 'https://primer.style/')
+ }
+
+ throw new Error(`Unexpected fetch request: ${url}`)
+ })
+
+ const result = await client.callTool({name: tool, arguments: {name: 'TextInput'}})
+
+ expect(result.isError).not.toBe(true)
+ expect(result.content).toContainEqual(
+ expect.objectContaining({
+ type: 'text',
+ text: expect.stringContaining(responseBody),
+ }),
+ )
+ expect(result.content).not.toContainEqual(
+ expect.objectContaining({
+ type: 'text',
+ text: expect.stringContaining(homepageBody),
+ }),
+ )
+ expect(fetchMock).toHaveBeenCalledTimes(1)
+ expect(fetchMock.mock.calls[0]?.[0].toString()).toBe(expectedUrl)
+ })
+
it.each(['ConfirmationDialog', 'confirmationdialog'])(
'retrieves ConfirmationDialog documentation for %s from its published URL',
async name => {
diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts
index 3440e40889b..9477b1da702 100644
--- a/packages/mcp/src/server.ts
+++ b/packages/mcp/src/server.ts
@@ -253,7 +253,7 @@ Use \`get_component\` for exactly one component and \`get_component_batch\` for
}
}
- const url = new URL(`/product/components/${match.id}`, 'https://primer.style')
+ const url = new URL(`/product/components/${match.slug}`, 'https://primer.style')
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Failed to fetch ${url}: ${response.statusText}`)
@@ -387,7 +387,7 @@ ${text}`,
}
}
- const url = new URL(`/product/components/${match.id}/accessibility`, 'https://primer.style')
+ const url = new URL(`/product/components/${match.slug}/accessibility`, 'https://primer.style')
const response = await fetch(url)
if (!response.ok) {
if ((response.status >= 400 && response.status < 500) || (response.status >= 300 && response.status < 400)) {