From 5d7a20700cb0842ebf304777158841afef734fdc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Jun 2026 12:23:29 +0000 Subject: [PATCH] Add readOnlyHint annotation to all MCP server tools Co-authored-by: timrogers <116134+timrogers@users.noreply.github.com> --- .changeset/mcp-readonly-tool-annotations.md | 5 +++ packages/mcp/src/server.ts | 34 +++++++++++++++++---- 2 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 .changeset/mcp-readonly-tool-annotations.md diff --git a/.changeset/mcp-readonly-tool-annotations.md b/.changeset/mcp-readonly-tool-annotations.md new file mode 100644 index 00000000000..472b78d4c92 --- /dev/null +++ b/.changeset/mcp-readonly-tool-annotations.md @@ -0,0 +1,5 @@ +--- +'@primer/mcp': patch +--- + +MCP server: Add `readOnlyHint` annotation to all tools, allowing them to run without human-in-the-loop approval in agentic environments such as Copilot cloud agent. diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts index 915e565fd69..dc1d2ccf0a1 100644 --- a/packages/mcp/src/server.ts +++ b/packages/mcp/src/server.ts @@ -39,6 +39,7 @@ server.registerTool( 'init', { description: 'Setup or create a project that includes Primer React', + annotations: {readOnlyHint: true}, }, async () => { const url = new URL(`/product/getting-started/react`, 'https://primer.style') @@ -92,7 +93,7 @@ ${text} // ----------------------------------------------------------------------------- server.registerTool( 'list_components', - {description: 'List all of the components available from Primer React'}, + {description: 'List all of the components available from Primer React', annotations: {readOnlyHint: true}}, async () => { const components = listComponents().map(component => { return `- ${component.name}` @@ -120,6 +121,7 @@ server.registerTool( inputSchema: { name: z.string().describe('The name of the component to retrieve'), }, + annotations: {readOnlyHint: true}, }, async ({name}) => { const components = listComponents() @@ -166,6 +168,7 @@ server.registerTool( inputSchema: { name: z.string().describe('The name of the component to retrieve'), }, + annotations: {readOnlyHint: true}, }, async ({name}) => { const components = listComponents() @@ -226,6 +229,7 @@ server.registerTool( inputSchema: { name: z.string().describe('The name of the component to retrieve'), }, + annotations: {readOnlyHint: true}, }, async ({name}) => { const components = listComponents() @@ -298,6 +302,7 @@ server.registerTool( inputSchema: { name: z.string().describe('The name of the component to retrieve'), }, + annotations: {readOnlyHint: true}, }, async ({name}) => { const components = listComponents() @@ -367,7 +372,7 @@ ${text}`, // ----------------------------------------------------------------------------- server.registerTool( 'list_patterns', - {description: 'List all of the patterns available from Primer React'}, + {description: 'List all of the patterns available from Primer React', annotations: {readOnlyHint: true}}, async () => { const patterns = listPatterns().map(pattern => { return `- ${pattern.name}` @@ -392,6 +397,7 @@ server.registerTool( inputSchema: { name: z.string().describe('The name of the pattern to retrieve'), }, + annotations: {readOnlyHint: true}, }, async ({name}) => { const patterns = listPatterns() @@ -469,6 +475,7 @@ server.registerTool( .default(15) .describe('Maximum results to return to stay within context limits'), }, + annotations: {readOnlyHint: true}, }, async ({query, group, limit}) => { // Resolve group via aliases @@ -566,6 +573,7 @@ server.registerTool( inputSchema: { groups: z.array(z.string()).describe('Array of group names (e.g., ["overlay", "shadow", "focus"])'), }, + annotations: {readOnlyHint: true}, }, async ({groups}) => { // Normalize and resolve aliases @@ -608,6 +616,7 @@ server.registerTool( { description: 'CRITICAL: CALL THIS FIRST. Provides the logic matrix and the list of valid group names. You cannot search accurately without this map.', + annotations: {readOnlyHint: true}, }, async () => { const groups = listTokenGroups() @@ -631,6 +640,7 @@ server.registerTool( { description: 'Provides "Golden Example" CSS for core patterns: Button (Interactions) and Stack (Layout). Use this to understand how to apply the Logic Matrix, Motion, and Spacing scales.', + annotations: {readOnlyHint: true}, }, async () => { const customPatterns = getTokenUsagePatternsText() @@ -659,6 +669,7 @@ server.registerTool( description: 'REQUIRED FINAL STEP. Use this to validate your CSS. You cannot complete a task involving CSS without a successful run of this tool.', inputSchema: {css: z.string()}, + annotations: {readOnlyHint: true}, }, async ({css}) => { try { @@ -694,7 +705,7 @@ server.registerTool( // ----------------------------------------------------------------------------- server.registerTool( 'get_color_usage', - {description: 'Get the guidelines for how to apply color to a user interface'}, + {description: 'Get the guidelines for how to apply color to a user interface', annotations: {readOnlyHint: true}}, async () => { const url = new URL(`/product/getting-started/foundations/color-usage`, 'https://primer.style') const response = await fetch(url) @@ -732,7 +743,10 @@ server.registerTool( server.registerTool( 'get_typography_usage', - {description: 'Get the guidelines for how to apply typography to a user interface'}, + { + description: 'Get the guidelines for how to apply typography to a user interface', + annotations: {readOnlyHint: true}, + }, async () => { const url = new URL(`/product/getting-started/foundations/typography`, 'https://primer.style') const response = await fetch(url) @@ -773,7 +787,10 @@ server.registerTool( // ----------------------------------------------------------------------------- server.registerTool( 'list_icons', - {description: 'List all of the icons (octicons) available from Primer Octicons React'}, + { + description: 'List all of the icons (octicons) available from Primer Octicons React', + annotations: {readOnlyHint: true}, + }, async () => { const icons = listIcons().map(icon => { const keywords = icon.keywords.map(keyword => { @@ -808,6 +825,7 @@ server.registerTool( name: z.string().describe('The name of the icon to retrieve'), size: z.string().optional().describe('The size of the icon to retrieve, e.g. "16"').default('16'), }, + annotations: {readOnlyHint: true}, }, async ({name, size}) => { const icons = listIcons() @@ -865,7 +883,10 @@ ${text}`, // ----------------------------------------------------------------------------- server.registerTool( 'primer_coding_guidelines', - {description: 'Get the guidelines when writing code that uses Primer or for UI code that you are creating'}, + { + description: 'Get the guidelines when writing code that uses Primer or for UI code that you are creating', + annotations: {readOnlyHint: true}, + }, async () => { return { content: [ @@ -922,6 +943,7 @@ server.registerTool( alt: z.string().describe('The alt text of the image being evaluated'), image: z.string().describe('The image URL or file path being evaluated'), }, + annotations: {readOnlyHint: true}, }, async ({surroundingText, alt, image}) => { // Call the LLM through MCP sampling