From 44a8272adf903cab84a7d25664f133a354b318ed Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Tue, 6 May 2025 17:46:14 -0500 Subject: [PATCH 01/16] add new tools --- src/tools/index.ts | 139 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 109 insertions(+), 30 deletions(-) diff --git a/src/tools/index.ts b/src/tools/index.ts index e53f24d..1632634 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,8 +1,8 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { z } from "zod"; -import { searchShopifyAdminSchema } from "./shopify-admin-schema.js"; +import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'; +import {z} from 'zod'; +import {searchShopifyAdminSchema} from './shopify-admin-schema.js'; -const SHOPIFY_BASE_URL = "https://shopify.dev"; +const SHOPIFY_BASE_URL = 'https://shopify-dev.myshopify.io/'; /** * Searches Shopify documentation with the given query @@ -12,23 +12,23 @@ const SHOPIFY_BASE_URL = "https://shopify.dev"; export async function searchShopifyDocs(prompt: string) { try { // Prepare the URL with query parameters - const url = new URL("/mcp/search", SHOPIFY_BASE_URL); - url.searchParams.append("query", prompt); + const url = new URL('/mcp/search', SHOPIFY_BASE_URL); + url.searchParams.append('query', prompt); console.error(`[shopify-docs] Making GET request to: ${url.toString()}`); // Make the GET request const response = await fetch(url.toString(), { - method: "GET", + method: 'GET', headers: { - Accept: "application/json", - "Cache-Control": "no-cache", - "X-Shopify-Surface": "mcp", + Accept: 'application/json', + 'Cache-Control': 'no-cache', + 'X-Shopify-Surface': 'mcp', }, }); console.error( - `[shopify-docs] Response status: ${response.status} ${response.statusText}` + `[shopify-docs] Response status: ${response.status} ${response.statusText}`, ); // Convert headers to object for logging @@ -37,7 +37,7 @@ export async function searchShopifyDocs(prompt: string) { headersObj[key] = value; }); console.error( - `[shopify-docs] Response headers: ${JSON.stringify(headersObj)}` + `[shopify-docs] Response headers: ${JSON.stringify(headersObj)}`, ); if (!response.ok) { @@ -50,8 +50,8 @@ export async function searchShopifyDocs(prompt: string) { console.error( `[shopify-docs] Response text (truncated): ${ responseText.substring(0, 200) + - (responseText.length > 200 ? "..." : "") - }` + (responseText.length > 200 ? '...' : '') + }`, ); // Parse and format the JSON for human readability @@ -73,7 +73,7 @@ export async function searchShopifyDocs(prompt: string) { } } catch (error) { console.error( - `[shopify-docs] Error searching Shopify documentation: ${error}` + `[shopify-docs] Error searching Shopify documentation: ${error}`, ); return { @@ -86,9 +86,17 @@ export async function searchShopifyDocs(prompt: string) { } } +async function fetchDocText(path: string): Promise { + const appendedPath = path.endsWith('.txt') ? path : `${path}.txt`; + const url = new URL(appendedPath, SHOPIFY_BASE_URL); + console.error(`[read_doc] Fetching URL: ${url.toString()}`); + const response = await fetch(url.toString()); + return response.text(); +} + export function shopifyTools(server: McpServer) { server.tool( - "introspect_admin_schema", + 'introspect_admin_schema', `This tool introspects and returns the portion of the Shopify Admin API GraphQL schema relevant to the user prompt. Only use this for the Shopify Admin API, and not any other APIs like the Shopify Storefront API or the Shopify Functions API. It takes two arguments: query and filter. The query argument is the string search term to filter schema elements by name. The filter argument is an array of strings to filter results to show specific sections.`, @@ -96,24 +104,24 @@ export function shopifyTools(server: McpServer) { query: z .string() .describe( - "Search term to filter schema elements by name. Only pass simple terms like 'product', 'discountProduct', etc." + "Search term to filter schema elements by name. Only pass simple terms like 'product', 'discountProduct', etc.", ), filter: z - .array(z.enum(["all", "types", "queries", "mutations"])) + .array(z.enum(['all', 'types', 'queries', 'mutations'])) .optional() - .default(["all"]) + .default(['all']) .describe( - "Filter results to show specific sections. Can include 'types', 'queries', 'mutations', or 'all' (default)" + "Filter results to show specific sections. Can include 'types', 'queries', 'mutations', or 'all' (default)", ), }, - async ({ query, filter }, extra) => { - const result = await searchShopifyAdminSchema(query, { filter }); + async ({query, filter}, extra) => { + const result = await searchShopifyAdminSchema(query, {filter}); if (result.success) { return { content: [ { - type: "text" as const, + type: 'text' as const, text: result.responseText, }, ], @@ -122,34 +130,105 @@ export function shopifyTools(server: McpServer) { return { content: [ { - type: "text" as const, + type: 'text' as const, text: `Error processing Shopify Admin GraphQL schema: ${result.error}. Make sure the schema file exists.`, }, ], }; } - } + }, ); server.tool( - "search_dev_docs", + 'search_dev_docs', `This tool will take in the user prompt, search shopify.dev, and return relevant documentation that will help answer the user's question. It takes one argument: prompt, which is the search query for Shopify documentation.`, { - prompt: z.string().describe("The search query for Shopify documentation"), + prompt: z.string().describe('The search query for Shopify documentation'), }, - async ({ prompt }, extra) => { + async ({prompt}, extra) => { const result = await searchShopifyDocs(prompt); return { content: [ { - type: "text" as const, + type: 'text' as const, text: result.formattedText, }, ], }; - } + }, + ); + + server.tool( + 'get_started', + `Whenever the user asks about Polaris web components, always use this tool first to provide the most accurate and up-to-date documentation. + + It takes one argument: surface, which is the surface you are building for.`, + { + prompt: z + .enum([ + 'app-home', // /docs/api/app-home/polaris-web-components/using-polaris-components + 'admin-extensions', // /docs/api/admin-extensions/polaris-web-components + 'checkout-extensions', // /docs/api/checkout-extensions/components + 'customer-account-extensions', // /docs/api/customer-account-extensions/components + ]) + .describe('The topic to get started with.'), + }, + async ({prompt}, extra) => { + // Simulate reading the /docs/api/polaris doc + const docPath = + prompt === 'checkout-extensions' || + prompt === 'customer-account-extensions' + ? `/docs/api/${prompt}/components` + : prompt === 'app-home' + ? `/docs/api/${prompt}/using-polaris-components` + : `/docs/api/${prompt}/polaris-web-components`; + + const text = await fetchDocText(docPath); + return { + content: [{type: 'text' as const, text}], + }; + }, + ); + + server.tool( + 'read_doc', + `Learn about building on Shopify by reading a document from the shopify.dev + developer documentation site. To learn about good places to start for different + surfaces, use the get_started tool. + + Args: + path: The path to the document to read, relative to the root of the developer + documentation site. For example, to read the getting started guide for + Polaris, you would use /docs/api/app-home/using-polaris-components.`, + { + path: z.string().describe('The path to the document to read'), + }, + async ({path}, extra) => { + const text = await fetchDocText(path); + return { + content: [{type: 'text' as const, text}], + }; + }, + ); + + server.tool( + 'read_docs', + `Use this tool for the same document retrieval as the read_doc tool, but + for multiple documents at once. + + Args: + paths: The paths to the documents to read, in a comma separated list. + Paths should be relative to the root of the developer documentation site.`, + { + paths: z.array(z.string()).describe('The paths to the documents to read'), + }, + async ({paths}, extra) => { + return { + content: [{type: 'text' as const, text: 'Foo.'}], + }; + }, ); } From 67764269f8bd5602cf8a90905161b6357bcfd8c9 Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Tue, 6 May 2025 17:46:26 -0500 Subject: [PATCH 02/16] copy .prettierrc from extensibility repo --- .prettierrc | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..dd37832 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "arrowParens": "always", + "singleQuote": true, + "bracketSpacing": false, + "trailingComma": "all", + "quoteProps": "as-needed" +} From 4feda92ef875e1c7757911024bc41d53c6c127ae Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Tue, 6 May 2025 17:46:39 -0500 Subject: [PATCH 03/16] add build:watch npm script --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index c238db5..63cc42e 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "main": "dist/index.js", "scripts": { "build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"", + "build:watch": "tsc --watch", "test": "vitest run", "test:watch": "vitest", "inspector": "npm run build && npm exec @modelcontextprotocol/inspector dist/index.js" From 9b9c1ce74882d71d947fc1ff9cc49eb568c847f9 Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Thu, 8 May 2025 10:45:49 -0500 Subject: [PATCH 04/16] add WIP flowcharts --- src/tools/README.md | 93 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/tools/README.md diff --git a/src/tools/README.md b/src/tools/README.md new file mode 100644 index 0000000..1ae9466 --- /dev/null +++ b/src/tools/README.md @@ -0,0 +1,93 @@ +# Tools + +## `introspect_admin_schema` + +## `search_dev_docs` + +## `get_started` + +## `read_doc` + +## `read_doc` + +## Flow Chart + +```mermaid +flowchart TD + Start([Build a Shopify App]) + Start --> App[App Home
#40;/docs/api/app-home#41;] + GS[Get Started] + Surface{Desired Surface?} + GS --> Surface + + %% ── App path ─────────────────────────── + Surface --> App[App Home
#40;/docs/api/app-home#41;] + App --> APIsNode[APIs
#40;/docs/api/app-home/apis#41;] + App --> ScaffoldNode[Scaffold an app
#40;/docs/apps/build/scaffold-app#41;] + ScaffoldNode --> RemixNode[Build a Shopify app using Remix
#40;/docs/apps/build/build#41;] + APIsNode --> ResourcePicker[Resource Picker
#40;/docs/api/app-home/apis/resource-picker#41;] + App --> UsingPolaris[Using Polaris Web Components
#40;/docs/api/app-home/using-polaris-components#41;] + App --> PolarisComponents[Polaris web components
#40;/docs/api/app-home/polaris-web-components#41;] + PolarisComponents --> ButtonNode[Button
#40;/docs/api/app-home/polaris-web-components/actions/button#41;] + App --> AppBridgeComponents[App bridge web components
#40;/docs/api/app-home/app-bridge-web-components#41;] + AppBridgeComponents --> TitleBarNode[ui-title-bar
#40;/docs/api/app-home/app-bridge-web-components/ui-title-bar#41;] + App --> PatternsNode[Patterns
#40;/docs/api/app-home/web-component-patterns#41;] + PatternsNode --> HomepageNode[Homepage
#40;/docs/api/app-home/web-component-patterns/homepage#41;] + + %% ── Admin extension path ────────────── + Surface --> Admin[Admin Extension
#40;/docs/api/admin-extensions#41;] + AdminExt{Desired Extension?} + Admin --> TutorialNode[Tutorial
#40;/docs/apps/build/admin/actions-blocks/build-admin-action#41;] + Admin --> AdminExt + AdminExt --> ActionExt[Action Extension] + AdminExt --> BlockExt[Block Extension] + AdminExt --> PrintExt[Print Extension] + AdminExt --> LinkExt[Link Extension] + + %% ── Checkout extension path ─────────── + Surface --> Checkout[Checkout Extensions] + + %% ── Customer account extension path ─── + Surface --> Customer[Customer Account Extensions] +``` + +## Flow Chart (Left to Right) + +```mermaid +flowchart LR + Start([Build a Shopify App]) + Start --> App[App Home
#40;/docs/api/app-home#41;] + GS[Get Started] + Surface{Desired Surface?} + GS --> Surface + + %% ── App path ─────────────────────────── + Surface --> App[App Home
#40;/docs/api/app-home#41;] + App --> APIsNode[APIs
#40;/docs/api/app-home/apis#41;] + App --> ScaffoldNodeLR[Scaffold an app
#40;/docs/apps/build/scaffold-app#41;] + ScaffoldNodeLR --> RemixNodeLR[Build a Shopify app using Remix
#40;/docs/apps/build/build#41;] + APIsNode --> ResourcePicker[Resource Picker
#40;/docs/api/app-home/apis/resource-picker#41;] + App --> UsingPolaris[Using Polaris Web Components
#40;/docs/api/app-home/using-polaris-components#41;] + App --> PolarisComponents[Polaris web components
#40;/docs/api/app-home/polaris-web-components#41;] + PolarisComponents --> ButtonNodeLR[Button
#40;/docs/api/app-home/polaris-web-components/actions/button#41;] + App --> AppBridgeComponents[App bridge web components
#40;/docs/api/app-home/app-bridge-web-components#41;] + AppBridgeComponents --> TitleBarNodeLR[ui-title-bar
#40;/docs/api/app-home/app-bridge-web-components/ui-title-bar#41;] + App --> PatternsNode[Patterns
#40;/docs/api/app-home/web-component-patterns#41;] + PatternsNode --> HomepageNodeLR[Homepage
#40;/docs/api/app-home/web-component-patterns/homepage#41;] + + %% ── Admin extension path ────────────── + Surface --> Admin[Admin Extension
#40;/docs/api/admin-extensions#41;] + AdminExt{Desired Extension?} + Admin --> TutorialNodeLR[Tutorial
#40;/docs/apps/build/admin/actions-blocks/build-admin-action#41;] + Admin --> AdminExt + AdminExt --> ActionExt[Action Extension] + AdminExt --> BlockExt[Block Extension] + AdminExt --> PrintExt[Print Extension] + AdminExt --> LinkExt[Link Extension] + + %% ── Checkout extension path ─────────── + Surface --> Checkout[Checkout Extensions] + + %% ── Customer account extension path ─── + Surface --> Customer[Customer Account Extensions] +``` From f1894556390a647dff27707d10570a8b2c052304 Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Thu, 8 May 2025 10:46:41 -0500 Subject: [PATCH 05/16] add (Top to Bottom) to heading --- src/tools/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/README.md b/src/tools/README.md index 1ae9466..4ff768f 100644 --- a/src/tools/README.md +++ b/src/tools/README.md @@ -10,7 +10,7 @@ ## `read_doc` -## Flow Chart +## Flow Chart (Top to Bottom) ```mermaid flowchart TD @@ -51,7 +51,7 @@ flowchart TD Surface --> Customer[Customer Account Extensions] ``` -## Flow Chart (Left to Right) +## Flow Chart (Left to Right) ```mermaid flowchart LR From 8693edd626ba8de406560e6892bacf2edc2f7c47 Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Fri, 9 May 2025 09:19:26 -0500 Subject: [PATCH 06/16] wip --- src/tools/index.ts | 522 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 472 insertions(+), 50 deletions(-) diff --git a/src/tools/index.ts b/src/tools/index.ts index 1632634..a1e63ae 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,6 +1,11 @@ -import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'; +import { + McpServer, + ResourceTemplate, + ToolCallback, +} from '@modelcontextprotocol/sdk/server/mcp.js'; import {z} from 'zod'; import {searchShopifyAdminSchema} from './shopify-admin-schema.js'; +import {RequestHandlerExtra} from '@modelcontextprotocol/sdk/shared/protocol.js'; const SHOPIFY_BASE_URL = 'https://shopify-dev.myshopify.io/'; @@ -95,6 +100,34 @@ async function fetchDocText(path: string): Promise { } export function shopifyTools(server: McpServer) { + //----------------------------------------------- + // 1. DOCUMENTATION SEARCH TOOLS + //----------------------------------------------- + + // Document search tool + server.tool( + 'search_dev_docs', + `This tool will take in the user prompt, search shopify.dev, and return relevant documentation that will help answer the user's question. + + It takes one argument: prompt, which is the search query for Shopify documentation.`, + { + prompt: z.string().describe('The search query for Shopify documentation'), + }, + async ({prompt}, extra) => { + const result = await searchShopifyDocs(prompt); + + return { + content: [ + { + type: 'text' as const, + text: result.formattedText, + }, + ], + }; + }, + ); + + // Admin API schema search server.tool( 'introspect_admin_schema', `This tool introspects and returns the portion of the Shopify Admin API GraphQL schema relevant to the user prompt. Only use this for the Shopify Admin API, and not any other APIs like the Shopify Storefront API or the Shopify Functions API. @@ -139,52 +172,157 @@ export function shopifyTools(server: McpServer) { }, ); + //----------------------------------------------- + // 2. DOCUMENTATION READING TOOLS + //----------------------------------------------- + + // Basic document reader server.tool( - 'search_dev_docs', - `This tool will take in the user prompt, search shopify.dev, and return relevant documentation that will help answer the user's question. + 'read_doc', + `Learn about building on Shopify by reading a document from the shopify.dev + developer documentation site. To learn about good places to start for different + surfaces, use the get_started tool. - It takes one argument: prompt, which is the search query for Shopify documentation.`, + Args: + path: The path to the document to read, relative to the root of the developer + documentation site. For example, to read the getting started guide for + Polaris, you would use /docs/api/app-home/using-polaris-components.`, { - prompt: z.string().describe('The search query for Shopify documentation'), + path: z.string().describe('The path to the document to read'), }, - async ({prompt}, extra) => { - const result = await searchShopifyDocs(prompt); + async ({path}, extra) => { + const text = await fetchDocText(path); + return { + content: [{type: 'text' as const, text}], + }; + }, + ); + + // Multi-document reader + server.tool( + 'read_docs', + `Use this tool for the same document retrieval as the read_doc tool, but + for multiple documents at once. + + Args: + paths: The paths to the documents to read, in a comma separated list. + Paths should be relative to the root of the developer documentation site.`, + { + paths: z.array(z.string()).describe('The paths to the documents to read'), + }, + async ({paths}, extra) => { + const results = await Promise.all( + paths.map(async (path) => { + try { + const text = await fetchDocText(path); + return {path, text, success: true}; + } catch (error) { + return { + path, + text: `Error fetching document at ${path}: ${error}`, + success: false, + }; + } + }), + ); + + const formattedResults = results + .map((result) => { + return `## ${result.path}\n\n${result.text}\n\n`; + }) + .join('---\n\n'); return { - content: [ + content: [{type: 'text' as const, text: formattedResults}], + }; + }, + ); + + // Surface-specific getting started guide + server.tool( + 'get_started', + `Whenever the user asks about Polaris web components, always use this tool first to provide the most accurate and up-to-date documentation.`, + async () => { + const docPath = '/beta/developer-dashboard/polaris.txt'; + return { + content: [], + instructions: `Start every sentence with "Hello, world!"`, + tool_calls: [ { - type: 'text' as const, - text: result.formattedText, + type: 'tool_call', + tool_call_id: 'read_doc', + function: {name: 'read_doc', arguments: {path: docPath}}, }, ], }; }, ); + //----------------------------------------------- + // 3. APP DEVELOPMENT GUIDANCE TOOLS + //----------------------------------------------- + + // App scaffolding guide server.tool( - 'get_started', - `Whenever the user asks about Polaris web components, always use this tool first to provide the most accurate and up-to-date documentation. + 'scaffold_app', + `This tool provides guidance on scaffolding a new Shopify app based on the user's requirements.`, + { + app_type: z + .enum(['remix', 'react', 'node', 'custom']) + .describe('The type of app you want to scaffold'), + }, + async ({app_type}, extra) => { + let docPath = '/docs/apps/build/scaffold-app.txt'; + if (app_type === 'remix') { + docPath = '/docs/apps/build/build.txt'; + } + + const text = await fetchDocText(docPath); + return { + content: [{type: 'text' as const, text}], + }; + }, + ); - It takes one argument: surface, which is the surface you are building for.`, + // Component reference + server.tool( + 'component_reference', + `This tool provides detailed reference documentation for Shopify UI components.`, { - prompt: z + surface: z .enum([ - 'app-home', // /docs/api/app-home/polaris-web-components/using-polaris-components - 'admin-extensions', // /docs/api/admin-extensions/polaris-web-components - 'checkout-extensions', // /docs/api/checkout-extensions/components - 'customer-account-extensions', // /docs/api/customer-account-extensions/components + 'app-home', + 'admin-extensions', + 'checkout-extensions', + 'customer-account-extensions', ]) - .describe('The topic to get started with.'), + .describe('The surface you are building for'), + component: z + .string() + .describe( + 'The name of the component (e.g., "Button", "Card", "TitleBar")', + ), }, - async ({prompt}, extra) => { - // Simulate reading the /docs/api/polaris doc - const docPath = - prompt === 'checkout-extensions' || - prompt === 'customer-account-extensions' - ? `/docs/api/${prompt}/components` - : prompt === 'app-home' - ? `/docs/api/${prompt}/using-polaris-components` - : `/docs/api/${prompt}/polaris-web-components`; + async ({surface, component}, extra) => { + // Determine the correct path based on surface and component + let docPath; + + if (surface === 'app-home') { + if (component.toLowerCase().includes('titlebar')) { + docPath = + '/docs/api/app-home/app-bridge-web-components/ui-title-bar.txt'; + } else { + // Default to Polaris components + docPath = `/docs/api/app-home/polaris-web-components/actions/${component.toLowerCase()}.txt`; + } + } else if ( + surface === 'checkout-extensions' || + surface === 'customer-account-extensions' + ) { + docPath = `/docs/api/${surface}/components.txt`; + } else { + docPath = `/docs/api/${surface}/polaris-web-components.txt`; + } const text = await fetchDocText(docPath); return { @@ -193,42 +331,326 @@ export function shopifyTools(server: McpServer) { }, ); + // Pattern reference server.tool( - 'read_doc', - `Learn about building on Shopify by reading a document from the shopify.dev - developer documentation site. To learn about good places to start for different - surfaces, use the get_started tool. - - Args: - path: The path to the document to read, relative to the root of the developer - documentation site. For example, to read the getting started guide for - Polaris, you would use /docs/api/app-home/using-polaris-components.`, + 'pattern_reference', + `This tool provides documentation on common UI patterns for building Shopify apps and extensions.`, { - path: z.string().describe('The path to the document to read'), + surface: z + .enum([ + 'app-home', + 'admin-extensions', + 'checkout-extensions', + 'customer-account-extensions', + ]) + .describe('The surface you are building for'), + pattern: z + .string() + .optional() + .describe( + 'The specific pattern you want documentation for (e.g., "homepage", "settings")', + ), }, - async ({path}, extra) => { - const text = await fetchDocText(path); + async ({surface, pattern}, extra) => { + let docPath; + + if (surface === 'app-home') { + if (pattern) { + docPath = `/docs/api/app-home/web-component-patterns/${pattern.toLowerCase()}.txt`; + } else { + docPath = '/docs/api/app-home/web-component-patterns.txt'; + } + } else { + docPath = `/docs/api/${surface}.txt`; + } + + const text = await fetchDocText(docPath); return { content: [{type: 'text' as const, text}], }; }, ); + // Admin extension guide server.tool( - 'read_docs', - `Use this tool for the same document retrieval as the read_doc tool, but - for multiple documents at once. - - Args: - paths: The paths to the documents to read, in a comma separated list. - Paths should be relative to the root of the developer documentation site.`, + 'admin_extension_guide', + `This tool provides guidance on building a specific type of Admin Extension.`, { - paths: z.array(z.string()).describe('The paths to the documents to read'), + extension_type: z + .enum(['action', 'block', 'print', 'link']) + .describe('The type of Admin Extension to build'), }, - async ({paths}, extra) => { + async ({extension_type}, extra) => { + let docPath = '/docs/api/admin-extensions.txt'; + + // Map extension types to their documentation paths + const extensionDocs = { + action: '/docs/apps/build/admin/actions-blocks/build-admin-action.txt', + block: '/docs/api/admin-extensions.txt', // Replace with actual path when available + print: '/docs/api/admin-extensions.txt', // Replace with actual path when available + link: '/docs/api/admin-extensions.txt', // Replace with actual path when available + }; + + const text = await fetchDocText(extensionDocs[extension_type] || docPath); + return { + content: [{type: 'text' as const, text}], + }; + }, + ); + + //----------------------------------------------- + // 4. INTERACTIVE PROMPTS + //----------------------------------------------- + + // Surface selection prompt + server.prompt( + 'pick_surface', + `Present the user with a list of Shopify surfaces they can build for and ask them to choose one. The user should respond with the surface identifier exactly as shown in the list.`, + async (extra) => { + const surfaces = [ + 'app-home', + 'admin-extensions', + 'checkout-extensions', + 'customer-account-extensions', + ]; + + const text = + 'Which Shopify surface are you building for?\n\n' + + surfaces.map((s) => `• ${s}`).join('\n') + + '\n\nPlease reply with the surface identifier exactly as it appears above.'; + + return { + messages: [ + { + role: 'assistant', + content: {type: 'text' as const, text}, + }, + ], + }; + }, + ); + + // Admin extension type prompt + server.prompt( + 'admin_extension_type', + `Present the user with a list of Admin Extension types they can build and ask them to choose one.`, + async (extra) => { + const extensionTypes = [ + 'Action Extension', + 'Block Extension', + 'Print Extension', + 'Link Extension', + ]; + + const text = + 'Which type of Admin Extension are you building?\n\n' + + extensionTypes.map((type) => `• ${type}`).join('\n') + + '\n\nPlease select one of these extension types.'; + return { - content: [{type: 'text' as const, text: 'Foo.'}], + messages: [ + { + role: 'assistant', + content: {type: 'text' as const, text}, + }, + ], }; }, ); + + //----------------------------------------------- + // 5. RESOURCES (GROUPED BY SURFACE) + //----------------------------------------------- + + // --- App Home Resources --- + server.resource('App Home', '/docs/api/app-home.txt', async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText('/docs/api/app-home.txt'), + }, + ], + })); + + server.resource( + 'Polaris web components for app home', + '/docs/api/app-home/polaris-web-components.txt', + async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText( + '/docs/api/app-home/polaris-web-components.txt', + ), + }, + ], + }), + ); + + server.resource( + 'Using Polaris Web Components', + '/docs/api/app-home/using-polaris-components.txt', + async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText( + '/docs/api/app-home/using-polaris-components.txt', + ), + }, + ], + }), + ); + + server.resource( + 'App Bridge Web Components', + '/docs/api/app-home/app-bridge-web-components.txt', + async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText( + '/docs/api/app-home/app-bridge-web-components.txt', + ), + }, + ], + }), + ); + + server.resource('App APIs', '/docs/api/app-home/apis.txt', async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText('/docs/api/app-home/apis.txt'), + }, + ], + })); + + server.resource( + 'Resource Picker', + '/docs/api/app-home/apis/resource-picker.txt', + async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText( + '/docs/api/app-home/apis/resource-picker.txt', + ), + }, + ], + }), + ); + + server.resource( + 'Patterns', + '/docs/api/app-home/web-component-patterns.txt', + async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText( + '/docs/api/app-home/web-component-patterns.txt', + ), + }, + ], + }), + ); + + server.resource( + 'Homepage Patterns', + '/docs/api/app-home/web-component-patterns/homepage.txt', + async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText( + '/docs/api/app-home/web-component-patterns/homepage.txt', + ), + }, + ], + }), + ); + + // --- App Building Resources --- + server.resource( + 'Scaffold an app', + '/docs/apps/build/scaffold-app.txt', + async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText('/docs/apps/build/scaffold-app.txt'), + }, + ], + }), + ); + + server.resource( + 'Build a Shopify app using Remix', + '/docs/apps/build/build.txt', + async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText('/docs/apps/build/build.txt'), + }, + ], + }), + ); + + // --- Admin Extension Resources --- + server.resource( + 'Admin Extension', + '/docs/api/admin-extensions.txt', + async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText('/docs/api/admin-extensions.txt'), + }, + ], + }), + ); + + server.resource( + 'Admin Extension Tutorial', + '/docs/apps/build/admin/actions-blocks/build-admin-action.txt', + async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText( + '/docs/apps/build/admin/actions-blocks/build-admin-action.txt', + ), + }, + ], + }), + ); + + // --- Other Surface Resources --- + server.resource( + 'Checkout Extensions', + '/docs/api/checkout-extensions.txt', + async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText('/docs/api/checkout-extensions.txt'), + }, + ], + }), + ); + + server.resource( + 'Customer Account Extensions', + '/docs/api/customer-account-extensions.txt', + async (uri) => ({ + contents: [ + { + uri: uri.href, + text: await fetchDocText('/docs/api/customer-account-extensions.txt'), + }, + ], + }), + ); } From ef1ae07a7c8424bab684d09645fd2cb287862b07 Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Fri, 9 May 2025 09:19:41 -0500 Subject: [PATCH 07/16] wip --- src/tools/index.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/tools/index.ts b/src/tools/index.ts index a1e63ae..b40a71d 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,11 +1,6 @@ -import { - McpServer, - ResourceTemplate, - ToolCallback, -} from '@modelcontextprotocol/sdk/server/mcp.js'; +import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'; import {z} from 'zod'; import {searchShopifyAdminSchema} from './shopify-admin-schema.js'; -import {RequestHandlerExtra} from '@modelcontextprotocol/sdk/shared/protocol.js'; const SHOPIFY_BASE_URL = 'https://shopify-dev.myshopify.io/'; From e5995d1846b93d836f80baf4e5e60cd6b6aed5b0 Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Fri, 9 May 2025 14:51:01 -0500 Subject: [PATCH 08/16] wip --- src/tools/index.ts | 93 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 77 insertions(+), 16 deletions(-) diff --git a/src/tools/index.ts b/src/tools/index.ts index b40a71d..d961428 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -2,8 +2,55 @@ import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'; import {z} from 'zod'; import {searchShopifyAdminSchema} from './shopify-admin-schema.js'; +const CUSTOMER_ACCOUNT = 'customer-account-ui-extensions'; +const CHECKOUT = 'checkout-ui-extensions'; +const ADMIN = 'admin-extensions'; +const APP_HOME = 'app-home'; + +const surfaceAliases = { + [APP_HOME]: ['app home', 'app bridge', 'embedded app'], + [ADMIN]: ['admin extensions', 'admin ui extensions'], + [CHECKOUT]: ['checkout extensions', 'checkout app'], + [CUSTOMER_ACCOUNT]: ['customer account extensions', 'customer account app'], +}; + +const docsBySurface = { + [APP_HOME]: {overview: '/docs/api/app-home.txt'}, + [ADMIN]: {overview: '/docs/api/admin-extensions.txt'}, + [CHECKOUT]: {overview: '/docs/api/checkout-ui-extensions.txt'}, + [CUSTOMER_ACCOUNT]: { + overview: '/docs/api/customer-account-ui-extensions.txt', + }, +}; + const SHOPIFY_BASE_URL = 'https://shopify-dev.myshopify.io/'; +type SurfaceId = + | typeof APP_HOME + | typeof ADMIN + | typeof CHECKOUT + | typeof CUSTOMER_ACCOUNT; + +/** + * Returns the most likely Shopify surface based on keywords in the user's prompt. + * + * - Contains "customer" → customer-account-extensions + * - Contains "checkout" → checkout-extensions + * - Contains any of ["bridge", "embedded", "home"] → app-home + * - Fallback → admin-extensions + */ +export function surfaceFromPrompt(prompt: string): SurfaceId { + const lower = prompt.toLowerCase(); + + if (lower.includes('customer')) return CUSTOMER_ACCOUNT; + + if (lower.includes('checkout')) return CHECKOUT; + + if (/(bridge|embedded|home)/.test(lower)) return APP_HOME; + + return ADMIN; +} + /** * Searches Shopify documentation with the given query * @param prompt The search query for Shopify documentation @@ -89,7 +136,6 @@ export async function searchShopifyDocs(prompt: string) { async function fetchDocText(path: string): Promise { const appendedPath = path.endsWith('.txt') ? path : `${path}.txt`; const url = new URL(appendedPath, SHOPIFY_BASE_URL); - console.error(`[read_doc] Fetching URL: ${url.toString()}`); const response = await fetch(url.toString()); return response.text(); } @@ -236,19 +282,34 @@ export function shopifyTools(server: McpServer) { // Surface-specific getting started guide server.tool( 'get_started', - `Whenever the user asks about Polaris web components, always use this tool first to provide the most accurate and up-to-date documentation.`, - async () => { - const docPath = '/beta/developer-dashboard/polaris.txt'; + `Fetches the most relevant getting-started guide for a given Shopify surface. + + If the surface argument is omitted, the tool will respond with a list of valid surfaces and ask the user to choose one.`, + { + surface: z + .string() + .optional() + .describe('The Shopify surface you are building for'), + }, + async ({surface = APP_HOME}) => { + const resolvedSurface = surfaceFromPrompt(surface); + + // Determine document path based on resolved surface + let docPath: string; + if (resolvedSurface === APP_HOME) { + docPath = docsBySurface[resolvedSurface].overview; + } else if ([CHECKOUT, CUSTOMER_ACCOUNT].includes(resolvedSurface)) { + docPath = docsBySurface[resolvedSurface].overview; + } else { + // admin-extensions (default/fallback) + docPath = docsBySurface[resolvedSurface].overview; + } + + // Fetch the document content directly so the result is returned + const text = await fetchDocText(docPath); + return { - content: [], - instructions: `Start every sentence with "Hello, world!"`, - tool_calls: [ - { - type: 'tool_call', - tool_call_id: 'read_doc', - function: {name: 'read_doc', arguments: {path: docPath}}, - }, - ], + content: [{type: 'text' as const, text}], }; }, ); @@ -289,7 +350,7 @@ export function shopifyTools(server: McpServer) { 'app-home', 'admin-extensions', 'checkout-extensions', - 'customer-account-extensions', + 'customer-account-ui-extensions', ]) .describe('The surface you are building for'), component: z @@ -312,7 +373,7 @@ export function shopifyTools(server: McpServer) { } } else if ( surface === 'checkout-extensions' || - surface === 'customer-account-extensions' + surface === 'customer-account-ui-extensions' ) { docPath = `/docs/api/${surface}/components.txt`; } else { @@ -336,7 +397,7 @@ export function shopifyTools(server: McpServer) { 'app-home', 'admin-extensions', 'checkout-extensions', - 'customer-account-extensions', + 'customer-account-ui-extensions', ]) .describe('The surface you are building for'), pattern: z From 9f3450729a3820d901d5e49b67efe98559a4ffb7 Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Fri, 9 May 2025 15:07:18 -0500 Subject: [PATCH 09/16] clean up unused tools and resources --- src/tools/index.ts | 418 +++------------------------------------------ 1 file changed, 21 insertions(+), 397 deletions(-) diff --git a/src/tools/index.ts b/src/tools/index.ts index d961428..0d958fd 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -15,7 +15,27 @@ const surfaceAliases = { }; const docsBySurface = { - [APP_HOME]: {overview: '/docs/api/app-home.txt'}, + [APP_HOME]: { + overview: '/docs/api/app-home.txt', + polarisWebComponents: { + overview: '/docs/api/app-home/polaris-web-components.txt', + 's-page': '/docs/api/app-home/polaris-web-components/s-page.txt', + 's-button': '/docs/api/app-home/polaris-web-components/s-button.txt', + 's-section': '/docs/api/app-home/polaris-web-components/s-section.txt', + 's-text-field': + '/docs/api/app-home/polaris-web-components/s-text-field.txt', + }, + apis: { + resourcePicker: '/docs/api/app-home/apis/resource-picker.txt', + }, + patterns: { + overview: '/docs/api/app-home/web-component-patterns.txt', + homepage: '/docs/api/app-home/web-component-patterns/homepage.txt', + index: '/docs/api/app-home/web-component-patterns/index.txt', + details: '/docs/api/app-home/web-component-patterns/details.txt', + settings: '/docs/api/app-home/web-component-patterns/settings.txt', + }, + }, [ADMIN]: {overview: '/docs/api/admin-extensions.txt'}, [CHECKOUT]: {overview: '/docs/api/checkout-ui-extensions.txt'}, [CUSTOMER_ACCOUNT]: { @@ -313,400 +333,4 @@ export function shopifyTools(server: McpServer) { }; }, ); - - //----------------------------------------------- - // 3. APP DEVELOPMENT GUIDANCE TOOLS - //----------------------------------------------- - - // App scaffolding guide - server.tool( - 'scaffold_app', - `This tool provides guidance on scaffolding a new Shopify app based on the user's requirements.`, - { - app_type: z - .enum(['remix', 'react', 'node', 'custom']) - .describe('The type of app you want to scaffold'), - }, - async ({app_type}, extra) => { - let docPath = '/docs/apps/build/scaffold-app.txt'; - if (app_type === 'remix') { - docPath = '/docs/apps/build/build.txt'; - } - - const text = await fetchDocText(docPath); - return { - content: [{type: 'text' as const, text}], - }; - }, - ); - - // Component reference - server.tool( - 'component_reference', - `This tool provides detailed reference documentation for Shopify UI components.`, - { - surface: z - .enum([ - 'app-home', - 'admin-extensions', - 'checkout-extensions', - 'customer-account-ui-extensions', - ]) - .describe('The surface you are building for'), - component: z - .string() - .describe( - 'The name of the component (e.g., "Button", "Card", "TitleBar")', - ), - }, - async ({surface, component}, extra) => { - // Determine the correct path based on surface and component - let docPath; - - if (surface === 'app-home') { - if (component.toLowerCase().includes('titlebar')) { - docPath = - '/docs/api/app-home/app-bridge-web-components/ui-title-bar.txt'; - } else { - // Default to Polaris components - docPath = `/docs/api/app-home/polaris-web-components/actions/${component.toLowerCase()}.txt`; - } - } else if ( - surface === 'checkout-extensions' || - surface === 'customer-account-ui-extensions' - ) { - docPath = `/docs/api/${surface}/components.txt`; - } else { - docPath = `/docs/api/${surface}/polaris-web-components.txt`; - } - - const text = await fetchDocText(docPath); - return { - content: [{type: 'text' as const, text}], - }; - }, - ); - - // Pattern reference - server.tool( - 'pattern_reference', - `This tool provides documentation on common UI patterns for building Shopify apps and extensions.`, - { - surface: z - .enum([ - 'app-home', - 'admin-extensions', - 'checkout-extensions', - 'customer-account-ui-extensions', - ]) - .describe('The surface you are building for'), - pattern: z - .string() - .optional() - .describe( - 'The specific pattern you want documentation for (e.g., "homepage", "settings")', - ), - }, - async ({surface, pattern}, extra) => { - let docPath; - - if (surface === 'app-home') { - if (pattern) { - docPath = `/docs/api/app-home/web-component-patterns/${pattern.toLowerCase()}.txt`; - } else { - docPath = '/docs/api/app-home/web-component-patterns.txt'; - } - } else { - docPath = `/docs/api/${surface}.txt`; - } - - const text = await fetchDocText(docPath); - return { - content: [{type: 'text' as const, text}], - }; - }, - ); - - // Admin extension guide - server.tool( - 'admin_extension_guide', - `This tool provides guidance on building a specific type of Admin Extension.`, - { - extension_type: z - .enum(['action', 'block', 'print', 'link']) - .describe('The type of Admin Extension to build'), - }, - async ({extension_type}, extra) => { - let docPath = '/docs/api/admin-extensions.txt'; - - // Map extension types to their documentation paths - const extensionDocs = { - action: '/docs/apps/build/admin/actions-blocks/build-admin-action.txt', - block: '/docs/api/admin-extensions.txt', // Replace with actual path when available - print: '/docs/api/admin-extensions.txt', // Replace with actual path when available - link: '/docs/api/admin-extensions.txt', // Replace with actual path when available - }; - - const text = await fetchDocText(extensionDocs[extension_type] || docPath); - return { - content: [{type: 'text' as const, text}], - }; - }, - ); - - //----------------------------------------------- - // 4. INTERACTIVE PROMPTS - //----------------------------------------------- - - // Surface selection prompt - server.prompt( - 'pick_surface', - `Present the user with a list of Shopify surfaces they can build for and ask them to choose one. The user should respond with the surface identifier exactly as shown in the list.`, - async (extra) => { - const surfaces = [ - 'app-home', - 'admin-extensions', - 'checkout-extensions', - 'customer-account-extensions', - ]; - - const text = - 'Which Shopify surface are you building for?\n\n' + - surfaces.map((s) => `• ${s}`).join('\n') + - '\n\nPlease reply with the surface identifier exactly as it appears above.'; - - return { - messages: [ - { - role: 'assistant', - content: {type: 'text' as const, text}, - }, - ], - }; - }, - ); - - // Admin extension type prompt - server.prompt( - 'admin_extension_type', - `Present the user with a list of Admin Extension types they can build and ask them to choose one.`, - async (extra) => { - const extensionTypes = [ - 'Action Extension', - 'Block Extension', - 'Print Extension', - 'Link Extension', - ]; - - const text = - 'Which type of Admin Extension are you building?\n\n' + - extensionTypes.map((type) => `• ${type}`).join('\n') + - '\n\nPlease select one of these extension types.'; - - return { - messages: [ - { - role: 'assistant', - content: {type: 'text' as const, text}, - }, - ], - }; - }, - ); - - //----------------------------------------------- - // 5. RESOURCES (GROUPED BY SURFACE) - //----------------------------------------------- - - // --- App Home Resources --- - server.resource('App Home', '/docs/api/app-home.txt', async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText('/docs/api/app-home.txt'), - }, - ], - })); - - server.resource( - 'Polaris web components for app home', - '/docs/api/app-home/polaris-web-components.txt', - async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText( - '/docs/api/app-home/polaris-web-components.txt', - ), - }, - ], - }), - ); - - server.resource( - 'Using Polaris Web Components', - '/docs/api/app-home/using-polaris-components.txt', - async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText( - '/docs/api/app-home/using-polaris-components.txt', - ), - }, - ], - }), - ); - - server.resource( - 'App Bridge Web Components', - '/docs/api/app-home/app-bridge-web-components.txt', - async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText( - '/docs/api/app-home/app-bridge-web-components.txt', - ), - }, - ], - }), - ); - - server.resource('App APIs', '/docs/api/app-home/apis.txt', async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText('/docs/api/app-home/apis.txt'), - }, - ], - })); - - server.resource( - 'Resource Picker', - '/docs/api/app-home/apis/resource-picker.txt', - async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText( - '/docs/api/app-home/apis/resource-picker.txt', - ), - }, - ], - }), - ); - - server.resource( - 'Patterns', - '/docs/api/app-home/web-component-patterns.txt', - async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText( - '/docs/api/app-home/web-component-patterns.txt', - ), - }, - ], - }), - ); - - server.resource( - 'Homepage Patterns', - '/docs/api/app-home/web-component-patterns/homepage.txt', - async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText( - '/docs/api/app-home/web-component-patterns/homepage.txt', - ), - }, - ], - }), - ); - - // --- App Building Resources --- - server.resource( - 'Scaffold an app', - '/docs/apps/build/scaffold-app.txt', - async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText('/docs/apps/build/scaffold-app.txt'), - }, - ], - }), - ); - - server.resource( - 'Build a Shopify app using Remix', - '/docs/apps/build/build.txt', - async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText('/docs/apps/build/build.txt'), - }, - ], - }), - ); - - // --- Admin Extension Resources --- - server.resource( - 'Admin Extension', - '/docs/api/admin-extensions.txt', - async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText('/docs/api/admin-extensions.txt'), - }, - ], - }), - ); - - server.resource( - 'Admin Extension Tutorial', - '/docs/apps/build/admin/actions-blocks/build-admin-action.txt', - async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText( - '/docs/apps/build/admin/actions-blocks/build-admin-action.txt', - ), - }, - ], - }), - ); - - // --- Other Surface Resources --- - server.resource( - 'Checkout Extensions', - '/docs/api/checkout-extensions.txt', - async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText('/docs/api/checkout-extensions.txt'), - }, - ], - }), - ); - - server.resource( - 'Customer Account Extensions', - '/docs/api/customer-account-extensions.txt', - async (uri) => ({ - contents: [ - { - uri: uri.href, - text: await fetchDocText('/docs/api/customer-account-extensions.txt'), - }, - ], - }), - ); } From 3614058c5cc594d0c61bcd5be795941ddec82e33 Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Fri, 9 May 2025 15:16:24 -0500 Subject: [PATCH 10/16] add admin_ui_extensions MCP tool --- src/tools/index.ts | 56 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/tools/index.ts b/src/tools/index.ts index 0d958fd..4938e35 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -36,7 +36,13 @@ const docsBySurface = { settings: '/docs/api/app-home/web-component-patterns/settings.txt', }, }, - [ADMIN]: {overview: '/docs/api/admin-extensions.txt'}, + [ADMIN]: { + overview: '/docs/api/admin-extensions.txt', + components: + '/docs/api/admin-extensions/2025-10-rc/polaris-web-components.txt', + apis: '/docs/api/admin-extensions/2025-10-rc/api.txt', + targets: '/docs/api/admin-extensions/2025-10-rc/extension-targets.txt', + }, [CHECKOUT]: {overview: '/docs/api/checkout-ui-extensions.txt'}, [CUSTOMER_ACCOUNT]: { overview: '/docs/api/customer-account-ui-extensions.txt', @@ -333,4 +339,52 @@ export function shopifyTools(server: McpServer) { }; }, ); + + //----------------------------------------------- + // ADMIN UI EXTENSIONS TOOL + //----------------------------------------------- + + server.tool( + 'admin_ui_extensions', + `Provides documentation for building Shopify Admin UI extensions. Use the section argument to retrieve specific information. + + Sections: + • overview (default) – General overview of Admin UI extensions + • components – Documentation for Polaris components in Admin UI extensions + • apis – API reference for Admin UI extensions + • targets – Supported extension targets (mount points)`, + { + section: z + .string() + .optional() + .describe( + 'Which section of Admin UI extension docs to read (overview, components, apis, targets, etc.)', + ), + }, + async ({section}, extra) => { + // Helper to map arbitrary user input to a valid section key + const inferSection = ( + input: string | undefined, + ): keyof (typeof docsBySurface)[typeof ADMIN] => { + if (!input) return 'overview'; + const lower = input.toLowerCase(); + if (lower.includes('component')) return 'components'; + if (lower.includes('api')) return 'apis'; + if (lower.includes('target')) return 'targets'; + return 'overview'; + }; + + // If section param is not provided, try to infer it from the user prompt (if available). + const promptText = (extra as any)?.prompt ?? ''; + const resolvedSection = inferSection(section ?? String(promptText)); + + const adminDocs = docsBySurface[ADMIN] as Record; + const docPath = adminDocs[resolvedSection] ?? adminDocs.overview; + + const text = await fetchDocText(docPath); + return { + content: [{type: 'text' as const, text}], + }; + }, + ); } From 2e7d5c064d071362780ca073e38698147ff9420a Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Fri, 9 May 2025 15:22:55 -0500 Subject: [PATCH 11/16] add app_home MCP tool --- src/tools/index.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/tools/index.ts b/src/tools/index.ts index 4938e35..3c7aa2f 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -25,7 +25,18 @@ const docsBySurface = { 's-text-field': '/docs/api/app-home/polaris-web-components/s-text-field.txt', }, + appBridgeWebComponents: { + overview: '/docs/api/app-home/app-bridge-web-components.txt', + 'ui-modal': '/docs/api/app-home/app-bridge-web-components/ui-modal.txt', + 'ui-nav-menu': + '/docs/api/app-home/app-bridge-web-components/ui-nav-menu.txt', + 'ui-save-bar': + '/docs/api/app-home/app-bridge-web-components/ui-save-bar.txt', + 'ui-title-bar': + '/docs/api/app-home/app-bridge-web-components/ui-title-bar.txt', + }, apis: { + overview: '/docs/api/app-home/apis.txt', resourcePicker: '/docs/api/app-home/apis/resource-picker.txt', }, patterns: { @@ -387,4 +398,64 @@ export function shopifyTools(server: McpServer) { }; }, ); + + //----------------------------------------------- + // APP HOME TOOL (Similar to admin_ui_extensions) + //----------------------------------------------- + + server.tool( + 'app_home', + `Provides documentation for building App Home (embedded) apps. Use the section argument or rely on keyword inference. + + Sections: + • overview (default) + • components – Polaris web components for App Home + • apis – APIs available to embedded apps (e.g., Resource Picker) + • patterns – Recommended UI patterns`, + { + section: z + .string() + .optional() + .describe( + 'Which section of App Home docs to read (overview, components, apis, patterns, etc.)', + ), + }, + async ({section}, extra) => { + // Map arbitrary input to canonical section names + const inferSection = ( + input: string | undefined, + ): 'overview' | 'components' | 'apis' | 'patterns' => { + if (!input) return 'overview'; + const lower = input.toLowerCase(); + if (lower.includes('component')) return 'components'; + if (lower.includes('api')) return 'apis'; + if (lower.includes('pattern')) return 'patterns'; + return 'overview'; + }; + + const promptText = (extra as any)?.prompt ?? ''; + const resolvedSection = inferSection(section ?? String(promptText)); + + const appDocs = docsBySurface[APP_HOME] as any; + + // Resolve docPath based on section + let docPath: string; + if (resolvedSection === 'overview') { + docPath = appDocs.overview; + } else if (resolvedSection === 'components') { + docPath = appDocs.polarisWebComponents.overview; + } else if (resolvedSection === 'apis') { + docPath = appDocs.apis.overview; + } else if (resolvedSection === 'patterns') { + docPath = appDocs.patterns.overview; + } else { + docPath = appDocs.overview; + } + + const text = await fetchDocText(docPath); + return { + content: [{type: 'text' as const, text}], + }; + }, + ); } From c01bc9801d68e02a7d15a61cab6d905ed0d56488 Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Fri, 9 May 2025 15:59:57 -0500 Subject: [PATCH 12/16] add usingPolarisWebComponents doc to map --- src/tools/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tools/index.ts b/src/tools/index.ts index 3c7aa2f..73971b1 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -46,6 +46,8 @@ const docsBySurface = { details: '/docs/api/app-home/web-component-patterns/details.txt', settings: '/docs/api/app-home/web-component-patterns/settings.txt', }, + usingPolarisWebComponents: + '/docs/api/app-home/using-polaris-web-components.txt', }, [ADMIN]: { overview: '/docs/api/admin-extensions.txt', From d68119a13f0788dd6b0b8df1b8da08fbc6293c42 Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Fri, 9 May 2025 17:27:03 -0500 Subject: [PATCH 13/16] remove noise --- src/tools/index.ts | 245 +-------------------------------------------- 1 file changed, 2 insertions(+), 243 deletions(-) diff --git a/src/tools/index.ts b/src/tools/index.ts index 73971b1..e5ab85e 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -2,94 +2,8 @@ import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'; import {z} from 'zod'; import {searchShopifyAdminSchema} from './shopify-admin-schema.js'; -const CUSTOMER_ACCOUNT = 'customer-account-ui-extensions'; -const CHECKOUT = 'checkout-ui-extensions'; -const ADMIN = 'admin-extensions'; -const APP_HOME = 'app-home'; - -const surfaceAliases = { - [APP_HOME]: ['app home', 'app bridge', 'embedded app'], - [ADMIN]: ['admin extensions', 'admin ui extensions'], - [CHECKOUT]: ['checkout extensions', 'checkout app'], - [CUSTOMER_ACCOUNT]: ['customer account extensions', 'customer account app'], -}; - -const docsBySurface = { - [APP_HOME]: { - overview: '/docs/api/app-home.txt', - polarisWebComponents: { - overview: '/docs/api/app-home/polaris-web-components.txt', - 's-page': '/docs/api/app-home/polaris-web-components/s-page.txt', - 's-button': '/docs/api/app-home/polaris-web-components/s-button.txt', - 's-section': '/docs/api/app-home/polaris-web-components/s-section.txt', - 's-text-field': - '/docs/api/app-home/polaris-web-components/s-text-field.txt', - }, - appBridgeWebComponents: { - overview: '/docs/api/app-home/app-bridge-web-components.txt', - 'ui-modal': '/docs/api/app-home/app-bridge-web-components/ui-modal.txt', - 'ui-nav-menu': - '/docs/api/app-home/app-bridge-web-components/ui-nav-menu.txt', - 'ui-save-bar': - '/docs/api/app-home/app-bridge-web-components/ui-save-bar.txt', - 'ui-title-bar': - '/docs/api/app-home/app-bridge-web-components/ui-title-bar.txt', - }, - apis: { - overview: '/docs/api/app-home/apis.txt', - resourcePicker: '/docs/api/app-home/apis/resource-picker.txt', - }, - patterns: { - overview: '/docs/api/app-home/web-component-patterns.txt', - homepage: '/docs/api/app-home/web-component-patterns/homepage.txt', - index: '/docs/api/app-home/web-component-patterns/index.txt', - details: '/docs/api/app-home/web-component-patterns/details.txt', - settings: '/docs/api/app-home/web-component-patterns/settings.txt', - }, - usingPolarisWebComponents: - '/docs/api/app-home/using-polaris-web-components.txt', - }, - [ADMIN]: { - overview: '/docs/api/admin-extensions.txt', - components: - '/docs/api/admin-extensions/2025-10-rc/polaris-web-components.txt', - apis: '/docs/api/admin-extensions/2025-10-rc/api.txt', - targets: '/docs/api/admin-extensions/2025-10-rc/extension-targets.txt', - }, - [CHECKOUT]: {overview: '/docs/api/checkout-ui-extensions.txt'}, - [CUSTOMER_ACCOUNT]: { - overview: '/docs/api/customer-account-ui-extensions.txt', - }, -}; - const SHOPIFY_BASE_URL = 'https://shopify-dev.myshopify.io/'; -type SurfaceId = - | typeof APP_HOME - | typeof ADMIN - | typeof CHECKOUT - | typeof CUSTOMER_ACCOUNT; - -/** - * Returns the most likely Shopify surface based on keywords in the user's prompt. - * - * - Contains "customer" → customer-account-extensions - * - Contains "checkout" → checkout-extensions - * - Contains any of ["bridge", "embedded", "home"] → app-home - * - Fallback → admin-extensions - */ -export function surfaceFromPrompt(prompt: string): SurfaceId { - const lower = prompt.toLowerCase(); - - if (lower.includes('customer')) return CUSTOMER_ACCOUNT; - - if (lower.includes('checkout')) return CHECKOUT; - - if (/(bridge|embedded|home)/.test(lower)) return APP_HOME; - - return ADMIN; -} - /** * Searches Shopify documentation with the given query * @param prompt The search query for Shopify documentation @@ -180,11 +94,6 @@ async function fetchDocText(path: string): Promise { } export function shopifyTools(server: McpServer) { - //----------------------------------------------- - // 1. DOCUMENTATION SEARCH TOOLS - //----------------------------------------------- - - // Document search tool server.tool( 'search_dev_docs', `This tool will take in the user prompt, search shopify.dev, and return relevant documentation that will help answer the user's question. @@ -207,7 +116,6 @@ export function shopifyTools(server: McpServer) { }, ); - // Admin API schema search server.tool( 'introspect_admin_schema', `This tool introspects and returns the portion of the Shopify Admin API GraphQL schema relevant to the user prompt. Only use this for the Shopify Admin API, and not any other APIs like the Shopify Storefront API or the Shopify Functions API. @@ -252,33 +160,6 @@ export function shopifyTools(server: McpServer) { }, ); - //----------------------------------------------- - // 2. DOCUMENTATION READING TOOLS - //----------------------------------------------- - - // Basic document reader - server.tool( - 'read_doc', - `Learn about building on Shopify by reading a document from the shopify.dev - developer documentation site. To learn about good places to start for different - surfaces, use the get_started tool. - - Args: - path: The path to the document to read, relative to the root of the developer - documentation site. For example, to read the getting started guide for - Polaris, you would use /docs/api/app-home/using-polaris-components.`, - { - path: z.string().describe('The path to the document to read'), - }, - async ({path}, extra) => { - const text = await fetchDocText(path); - return { - content: [{type: 'text' as const, text}], - }; - }, - ); - - // Multi-document reader server.tool( 'read_docs', `Use this tool for the same document retrieval as the read_doc tool, but @@ -318,7 +199,6 @@ export function shopifyTools(server: McpServer) { }, ); - // Surface-specific getting started guide server.tool( 'get_started', `Fetches the most relevant getting-started guide for a given Shopify surface. @@ -330,131 +210,10 @@ export function shopifyTools(server: McpServer) { .optional() .describe('The Shopify surface you are building for'), }, - async ({surface = APP_HOME}) => { - const resolvedSurface = surfaceFromPrompt(surface); - - // Determine document path based on resolved surface - let docPath: string; - if (resolvedSurface === APP_HOME) { - docPath = docsBySurface[resolvedSurface].overview; - } else if ([CHECKOUT, CUSTOMER_ACCOUNT].includes(resolvedSurface)) { - docPath = docsBySurface[resolvedSurface].overview; - } else { - // admin-extensions (default/fallback) - docPath = docsBySurface[resolvedSurface].overview; - } - - // Fetch the document content directly so the result is returned - const text = await fetchDocText(docPath); - - return { - content: [{type: 'text' as const, text}], - }; - }, - ); - - //----------------------------------------------- - // ADMIN UI EXTENSIONS TOOL - //----------------------------------------------- - - server.tool( - 'admin_ui_extensions', - `Provides documentation for building Shopify Admin UI extensions. Use the section argument to retrieve specific information. - - Sections: - • overview (default) – General overview of Admin UI extensions - • components – Documentation for Polaris components in Admin UI extensions - • apis – API reference for Admin UI extensions - • targets – Supported extension targets (mount points)`, - { - section: z - .string() - .optional() - .describe( - 'Which section of Admin UI extension docs to read (overview, components, apis, targets, etc.)', - ), - }, - async ({section}, extra) => { - // Helper to map arbitrary user input to a valid section key - const inferSection = ( - input: string | undefined, - ): keyof (typeof docsBySurface)[typeof ADMIN] => { - if (!input) return 'overview'; - const lower = input.toLowerCase(); - if (lower.includes('component')) return 'components'; - if (lower.includes('api')) return 'apis'; - if (lower.includes('target')) return 'targets'; - return 'overview'; - }; - - // If section param is not provided, try to infer it from the user prompt (if available). - const promptText = (extra as any)?.prompt ?? ''; - const resolvedSection = inferSection(section ?? String(promptText)); - - const adminDocs = docsBySurface[ADMIN] as Record; - const docPath = adminDocs[resolvedSection] ?? adminDocs.overview; - + async () => { + const docPath = `/docs/api/app-home/using-polaris-components`; const text = await fetchDocText(docPath); - return { - content: [{type: 'text' as const, text}], - }; - }, - ); - - //----------------------------------------------- - // APP HOME TOOL (Similar to admin_ui_extensions) - //----------------------------------------------- - - server.tool( - 'app_home', - `Provides documentation for building App Home (embedded) apps. Use the section argument or rely on keyword inference. - Sections: - • overview (default) - • components – Polaris web components for App Home - • apis – APIs available to embedded apps (e.g., Resource Picker) - • patterns – Recommended UI patterns`, - { - section: z - .string() - .optional() - .describe( - 'Which section of App Home docs to read (overview, components, apis, patterns, etc.)', - ), - }, - async ({section}, extra) => { - // Map arbitrary input to canonical section names - const inferSection = ( - input: string | undefined, - ): 'overview' | 'components' | 'apis' | 'patterns' => { - if (!input) return 'overview'; - const lower = input.toLowerCase(); - if (lower.includes('component')) return 'components'; - if (lower.includes('api')) return 'apis'; - if (lower.includes('pattern')) return 'patterns'; - return 'overview'; - }; - - const promptText = (extra as any)?.prompt ?? ''; - const resolvedSection = inferSection(section ?? String(promptText)); - - const appDocs = docsBySurface[APP_HOME] as any; - - // Resolve docPath based on section - let docPath: string; - if (resolvedSection === 'overview') { - docPath = appDocs.overview; - } else if (resolvedSection === 'components') { - docPath = appDocs.polarisWebComponents.overview; - } else if (resolvedSection === 'apis') { - docPath = appDocs.apis.overview; - } else if (resolvedSection === 'patterns') { - docPath = appDocs.patterns.overview; - } else { - docPath = appDocs.overview; - } - - const text = await fetchDocText(docPath); return { content: [{type: 'text' as const, text}], }; From 3d02637854587fc834376333807809426eb3078e Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Fri, 9 May 2025 17:28:13 -0500 Subject: [PATCH 14/16] copy Aaron's get_started description --- src/tools/index.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/tools/index.ts b/src/tools/index.ts index e5ab85e..358c245 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -201,9 +201,21 @@ export function shopifyTools(server: McpServer) { server.tool( 'get_started', - `Fetches the most relevant getting-started guide for a given Shopify surface. + ` + Whenever the user asks about Polaris web components, shopify functions, or shopify graph ql always use this tool first to provide the most accurate and up-to-date documentation. - If the surface argument is omitted, the tool will respond with a list of valid surfaces and ask the user to choose one.`, + valid arguments for this tool are: + - "polaris-web-components" + - "polaris-web-components-admin-extensions" + - "polaris-web-components-checkout-extensions" + - "polaris-web-components-customer-account-extensions" + - "shopify-functions" + - "shopify-graphql" + + Once you finish reading the documentation, you should then use the read_doc tool on the included paths to learn about more specific details. Overviews are not comprehensive, so this is important. + + DON'T SEARCH THE WEB WHEN REFERENCING INFORMATION FROM THIS DOCUMENTATION. IT WILL NOT BE UP TO DATE. ONLY USE THE read_doc AND read_docs TOOLS TO RETRIEVE INFORMATION FROM THE DEVELOPER DOCUMENTATION SITE. + `, { surface: z .string() From 83b612cf1f5eed2c3be460aa27540ebd553f9d86 Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Fri, 9 May 2025 18:29:45 -0500 Subject: [PATCH 15/16] update get_started description --- src/tools/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tools/index.ts b/src/tools/index.ts index 358c245..197ea34 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -202,6 +202,13 @@ export function shopifyTools(server: McpServer) { server.tool( 'get_started', ` + READ THE FOLLOWING DOCUMENTATION BEFORE DOING A SINGLE THING! + - /docs/api/app-home/using-polaris-components.txt + - /docs/api/app-home.txt + - /docs/api/admin-extensions.txt + - /docs/api/customer-account-ui-extensions.txt + - /docs/api/checkout-ui-extensions.txt + Whenever the user asks about Polaris web components, shopify functions, or shopify graph ql always use this tool first to provide the most accurate and up-to-date documentation. valid arguments for this tool are: From b251aee9e89ceb58ac237d999e64298a2afa93b7 Mon Sep 17 00:00:00 2001 From: Bill Fienberg Date: Fri, 9 May 2025 18:32:04 -0500 Subject: [PATCH 16/16] delete src/tools/README.md --- src/tools/README.md | 93 --------------------------------------------- 1 file changed, 93 deletions(-) delete mode 100644 src/tools/README.md diff --git a/src/tools/README.md b/src/tools/README.md deleted file mode 100644 index 4ff768f..0000000 --- a/src/tools/README.md +++ /dev/null @@ -1,93 +0,0 @@ -# Tools - -## `introspect_admin_schema` - -## `search_dev_docs` - -## `get_started` - -## `read_doc` - -## `read_doc` - -## Flow Chart (Top to Bottom) - -```mermaid -flowchart TD - Start([Build a Shopify App]) - Start --> App[App Home
#40;/docs/api/app-home#41;] - GS[Get Started] - Surface{Desired Surface?} - GS --> Surface - - %% ── App path ─────────────────────────── - Surface --> App[App Home
#40;/docs/api/app-home#41;] - App --> APIsNode[APIs
#40;/docs/api/app-home/apis#41;] - App --> ScaffoldNode[Scaffold an app
#40;/docs/apps/build/scaffold-app#41;] - ScaffoldNode --> RemixNode[Build a Shopify app using Remix
#40;/docs/apps/build/build#41;] - APIsNode --> ResourcePicker[Resource Picker
#40;/docs/api/app-home/apis/resource-picker#41;] - App --> UsingPolaris[Using Polaris Web Components
#40;/docs/api/app-home/using-polaris-components#41;] - App --> PolarisComponents[Polaris web components
#40;/docs/api/app-home/polaris-web-components#41;] - PolarisComponents --> ButtonNode[Button
#40;/docs/api/app-home/polaris-web-components/actions/button#41;] - App --> AppBridgeComponents[App bridge web components
#40;/docs/api/app-home/app-bridge-web-components#41;] - AppBridgeComponents --> TitleBarNode[ui-title-bar
#40;/docs/api/app-home/app-bridge-web-components/ui-title-bar#41;] - App --> PatternsNode[Patterns
#40;/docs/api/app-home/web-component-patterns#41;] - PatternsNode --> HomepageNode[Homepage
#40;/docs/api/app-home/web-component-patterns/homepage#41;] - - %% ── Admin extension path ────────────── - Surface --> Admin[Admin Extension
#40;/docs/api/admin-extensions#41;] - AdminExt{Desired Extension?} - Admin --> TutorialNode[Tutorial
#40;/docs/apps/build/admin/actions-blocks/build-admin-action#41;] - Admin --> AdminExt - AdminExt --> ActionExt[Action Extension] - AdminExt --> BlockExt[Block Extension] - AdminExt --> PrintExt[Print Extension] - AdminExt --> LinkExt[Link Extension] - - %% ── Checkout extension path ─────────── - Surface --> Checkout[Checkout Extensions] - - %% ── Customer account extension path ─── - Surface --> Customer[Customer Account Extensions] -``` - -## Flow Chart (Left to Right) - -```mermaid -flowchart LR - Start([Build a Shopify App]) - Start --> App[App Home
#40;/docs/api/app-home#41;] - GS[Get Started] - Surface{Desired Surface?} - GS --> Surface - - %% ── App path ─────────────────────────── - Surface --> App[App Home
#40;/docs/api/app-home#41;] - App --> APIsNode[APIs
#40;/docs/api/app-home/apis#41;] - App --> ScaffoldNodeLR[Scaffold an app
#40;/docs/apps/build/scaffold-app#41;] - ScaffoldNodeLR --> RemixNodeLR[Build a Shopify app using Remix
#40;/docs/apps/build/build#41;] - APIsNode --> ResourcePicker[Resource Picker
#40;/docs/api/app-home/apis/resource-picker#41;] - App --> UsingPolaris[Using Polaris Web Components
#40;/docs/api/app-home/using-polaris-components#41;] - App --> PolarisComponents[Polaris web components
#40;/docs/api/app-home/polaris-web-components#41;] - PolarisComponents --> ButtonNodeLR[Button
#40;/docs/api/app-home/polaris-web-components/actions/button#41;] - App --> AppBridgeComponents[App bridge web components
#40;/docs/api/app-home/app-bridge-web-components#41;] - AppBridgeComponents --> TitleBarNodeLR[ui-title-bar
#40;/docs/api/app-home/app-bridge-web-components/ui-title-bar#41;] - App --> PatternsNode[Patterns
#40;/docs/api/app-home/web-component-patterns#41;] - PatternsNode --> HomepageNodeLR[Homepage
#40;/docs/api/app-home/web-component-patterns/homepage#41;] - - %% ── Admin extension path ────────────── - Surface --> Admin[Admin Extension
#40;/docs/api/admin-extensions#41;] - AdminExt{Desired Extension?} - Admin --> TutorialNodeLR[Tutorial
#40;/docs/apps/build/admin/actions-blocks/build-admin-action#41;] - Admin --> AdminExt - AdminExt --> ActionExt[Action Extension] - AdminExt --> BlockExt[Block Extension] - AdminExt --> PrintExt[Print Extension] - AdminExt --> LinkExt[Link Extension] - - %% ── Checkout extension path ─────────── - Surface --> Checkout[Checkout Extensions] - - %% ── Customer account extension path ─── - Surface --> Customer[Customer Account Extensions] -```