From 523d5ea5df037ed7a33d9a3c44dcbbaa7d9dcc51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:16:55 +0000 Subject: [PATCH 1/2] fix(mcp): use scenario pattern llms endpoints Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- .changeset/mcp-scenario-pattern-llms.md | 5 ++++ packages/mcp/src/server.test.ts | 28 ++++++++++++++++++++ packages/mcp/src/server.ts | 34 +++++++++++++++++++------ 3 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 .changeset/mcp-scenario-pattern-llms.md diff --git a/.changeset/mcp-scenario-pattern-llms.md b/.changeset/mcp-scenario-pattern-llms.md new file mode 100644 index 00000000000..b6fe27ffc16 --- /dev/null +++ b/.changeset/mcp-scenario-pattern-llms.md @@ -0,0 +1,5 @@ +--- +'@primer/mcp': patch +--- + +MCP server: Load scenario pattern listings and guidance from Primer's dedicated LLM endpoints. diff --git a/packages/mcp/src/server.test.ts b/packages/mcp/src/server.test.ts index a20e6a97429..b73be6a25bb 100644 --- a/packages/mcp/src/server.test.ts +++ b/packages/mcp/src/server.test.ts @@ -162,6 +162,34 @@ describe('MCP server', () => { expect(vi.getTimerCount()).toBe(0) }) + it('lists scenario patterns from the scenario patterns llms.txt endpoint', async () => { + const fetchMock = vi.mocked(fetch) + fetchMock.mockResolvedValue(new Response('# Scenario patterns\n\n- [Copy](./copy)')) + + const result = await client.callTool({name: 'list_patterns'}) + + expect(fetchMock).toHaveBeenCalledWith(new URL('https://primer.style/product/scenario-patterns/llms.txt')) + expect(result.content).toContainEqual( + expect.objectContaining({ + type: 'text', + text: expect.stringContaining('# Scenario patterns\n\n- [Copy](./copy)'), + }), + ) + }) + + it('gets scenario patterns from their llms.txt endpoints', async () => { + const fetchMock = vi.mocked(fetch) + fetchMock.mockResolvedValue(new Response('# Copy\n\nCopy guidance')) + + const result = await client.callTool({ + name: 'get_pattern', + arguments: {name: 'Copy'}, + }) + + expect(fetchMock).toHaveBeenCalledWith(new URL('https://primer.style/product/scenario-patterns/copy/llms.txt')) + expect(result.content).toEqual([{type: 'text', text: '# Copy\n\nCopy guidance'}]) + }) + it('reviews alt text through the multi-round-trip sampling flow', async () => { const result = await client.callTool({ name: 'review_alt_text', diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts index f1e031a27ed..be1de743258 100644 --- a/packages/mcp/src/server.ts +++ b/packages/mcp/src/server.ts @@ -441,13 +441,19 @@ ${text}`, 'list_patterns', { description: - 'List all of the patterns available from Primer React. Scenario patterns describe specific user tasks (copy, delete, filter, search). Prefer a scenario pattern when one fits the task, and fall back to the more generic UI patterns otherwise.', + 'List all of the patterns available from Primer React. Scenario patterns describe specific user tasks. Prefer a scenario pattern when one fits the task, and fall back to the more generic UI patterns otherwise.', annotations: {readOnlyHint: true}, }, async () => { const all = listPatterns() - const scenario = all.filter(pattern => pattern.category === 'scenario').map(pattern => `- ${pattern.name}`) const ui = all.filter(pattern => pattern.category === 'ui').map(pattern => `- ${pattern.name}`) + const url = new URL('/product/scenario-patterns/llms.txt', 'https://primer.style') + const response = await fetch(url) + if (!response.ok) { + throw new Error(`Failed to fetch ${url} - ${response.statusText}`) + } + + const scenario = await response.text() return { content: [ { @@ -456,7 +462,7 @@ ${text}`, ## Scenario patterns -${scenario.join('\n')} +${scenario} ## UI patterns @@ -471,7 +477,7 @@ ${ui.join('\n')}`, 'get_pattern', { description: - 'Get a specific pattern by name. Scenario patterns describe specific user tasks (copy, delete, filter, search). Prefer a scenario pattern when one fits the task, and fall back to the more generic UI patterns otherwise.', + 'Get a specific pattern by name. Scenario patterns describe specific user tasks. Prefer a scenario pattern when one fits the task, and fall back to the more generic UI patterns otherwise.', inputSchema: z.object({ name: z.string().describe('The name of the pattern to retrieve'), }), @@ -495,20 +501,32 @@ ${ui.join('\n')}`, } const basePath = match.category === 'scenario' ? 'scenario-patterns' : 'ui-patterns' - const url = new URL(`/product/${basePath}/${match.id}`, 'https://primer.style') + const suffix = match.category === 'scenario' ? '/llms.txt' : '' + const url = new URL(`/product/${basePath}/${match.id}${suffix}`, 'https://primer.style') const response = await fetch(url) if (!response.ok) { throw new Error(`Failed to fetch ${url} - ${response.statusText}`) } - const html = await response.text() - if (!html) { + const body = await response.text() + if (!body) { return { content: [], } } - const $ = cheerio.load(html) + if (match.category === 'scenario') { + return { + content: [ + { + type: 'text', + text: body, + }, + ], + } + } + + const $ = cheerio.load(body) const source = $('main').html() if (!source) { return { From f64427fc756ed6374e89392b04141384b2ea8b75 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 27 Aug 2026 16:29:56 -0500 Subject: [PATCH 2/2] Update MCP version from patch to minor --- .changeset/mcp-scenario-pattern-llms.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/mcp-scenario-pattern-llms.md b/.changeset/mcp-scenario-pattern-llms.md index b6fe27ffc16..eb848b6da7a 100644 --- a/.changeset/mcp-scenario-pattern-llms.md +++ b/.changeset/mcp-scenario-pattern-llms.md @@ -1,5 +1,5 @@ --- -'@primer/mcp': patch +'@primer/mcp': minor --- -MCP server: Load scenario pattern listings and guidance from Primer's dedicated LLM endpoints. +Load scenario pattern listings and guidance from Primer's dedicated LLM endpoints.