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)) {