@primer/mcp 0.5.1 → 1.0.0-rc.3fb350b1f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -2
- package/dist/{server-Dv5XFnmA.js → server-CBy5I-Dh.js} +449 -436
- package/dist/stdio.js +3 -4
- package/package.json +8 -4
- package/src/index.ts +1 -1
- package/src/server.test.ts +37 -4
- package/src/server.ts +853 -833
- package/src/transports/stdio.ts +3 -5
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
2
|
+
import { McpServer, inputRequired, inputResponse } from "@modelcontextprotocol/server";
|
|
3
3
|
import * as cheerio from "cheerio";
|
|
4
4
|
import * as z from "zod";
|
|
5
5
|
import TurndownService from "turndown";
|
|
@@ -395,10 +395,12 @@ function loadAllTokensWithGuidelines() {
|
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
397
|
function loadDesignTokensGuide() {
|
|
398
|
-
|
|
398
|
+
const guidePath = createRequire(import.meta.url).resolve("@primer/primitives/DESIGN_TOKENS_GUIDE.md");
|
|
399
|
+
return readFileSync(guidePath, "utf-8");
|
|
399
400
|
}
|
|
400
401
|
function loadDesignTokensSpec() {
|
|
401
|
-
|
|
402
|
+
const specPath = createRequire(import.meta.url).resolve("@primer/primitives/DESIGN_TOKENS_SPEC.md");
|
|
403
|
+
return readFileSync(specPath, "utf-8");
|
|
402
404
|
}
|
|
403
405
|
function getDesignTokenSpecsText(groups) {
|
|
404
406
|
return `
|
|
@@ -689,27 +691,31 @@ function runStylelint(css) {
|
|
|
689
691
|
});
|
|
690
692
|
}
|
|
691
693
|
//#endregion
|
|
694
|
+
//#region package.json
|
|
695
|
+
var version = "1.0.0";
|
|
696
|
+
//#endregion
|
|
692
697
|
//#region src/server.ts
|
|
693
|
-
const server = new McpServer({
|
|
694
|
-
name: "Primer",
|
|
695
|
-
version: "0.5.1"
|
|
696
|
-
});
|
|
697
698
|
const turndownService = new TurndownService();
|
|
698
699
|
const allTokensWithGuidelines = loadAllTokensWithGuidelines();
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
700
|
+
function createServer() {
|
|
701
|
+
const server = new McpServer({
|
|
702
|
+
name: "Primer",
|
|
703
|
+
version
|
|
704
|
+
});
|
|
705
|
+
server.registerTool("init", {
|
|
706
|
+
description: "Setup or create a project that includes Primer React",
|
|
707
|
+
annotations: { readOnlyHint: true }
|
|
708
|
+
}, async () => {
|
|
709
|
+
const url = new URL(`/product/getting-started/react`, "https://primer.style");
|
|
710
|
+
const response = await fetch(url);
|
|
711
|
+
if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
|
|
712
|
+
const html = await response.text();
|
|
713
|
+
if (!html) return { content: [] };
|
|
714
|
+
const source = cheerio.load(html)("main").html();
|
|
715
|
+
if (!source) return { content: [] };
|
|
716
|
+
return { content: [{
|
|
717
|
+
type: "text",
|
|
718
|
+
text: `The getting started documentation for Primer React is included below. It's important that the project:
|
|
713
719
|
|
|
714
720
|
- Is using a tool like Vite, Next.js, etc that supports TypeScript and React. If the project does not have support for that, generate an appropriate project scaffold
|
|
715
721
|
- Installs the latest version of \`@primer/react\` from \`npm\`
|
|
@@ -722,187 +728,187 @@ server.registerTool("init", {
|
|
|
722
728
|
|
|
723
729
|
${turndownService.turndown(source)}
|
|
724
730
|
`
|
|
725
|
-
|
|
726
|
-
});
|
|
727
|
-
server.registerTool("list_components", {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
}, async () => {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
731
|
+
}] };
|
|
732
|
+
});
|
|
733
|
+
server.registerTool("list_components", {
|
|
734
|
+
description: "List all of the components available from Primer React",
|
|
735
|
+
annotations: { readOnlyHint: true }
|
|
736
|
+
}, async () => {
|
|
737
|
+
return { content: [{
|
|
738
|
+
type: "text",
|
|
739
|
+
text: `The following components are available in the @primer/react in TypeScript projects:
|
|
734
740
|
|
|
735
741
|
${listComponents().map((component) => {
|
|
736
|
-
|
|
737
|
-
|
|
742
|
+
return `- ${component.name}`;
|
|
743
|
+
}).join("\n")}
|
|
738
744
|
|
|
739
745
|
Use \`get_component\` for exactly one component and \`get_component_batch\` for 2 to 10 components. You can use these components from the @primer/react package.`
|
|
740
|
-
}] };
|
|
741
|
-
});
|
|
742
|
-
server.registerTool("get_component", {
|
|
743
|
-
description: "Retrieve official documentation and usage details for exactly one React component from the @primer/react package. Use get_component_batch for 2 to 10 components.",
|
|
744
|
-
inputSchema: { name: z.string().describe("The name of the component to retrieve") },
|
|
745
|
-
annotations: { readOnlyHint: true }
|
|
746
|
-
}, async ({ name }) => {
|
|
747
|
-
const match = listComponents().find((component) => {
|
|
748
|
-
return component.name === name || component.name.toLowerCase() === name.toLowerCase();
|
|
749
|
-
});
|
|
750
|
-
if (!match) return {
|
|
751
|
-
isError: true,
|
|
752
|
-
errorMessage: `There is no component named \`${name}\` in the @primer/react package. For a full list of components, use the \`list_components\` tool.`,
|
|
753
|
-
content: []
|
|
754
|
-
};
|
|
755
|
-
try {
|
|
756
|
-
const llmsUrl = new URL(`/product/components/${match.slug}/llms.txt`, "https://primer.style");
|
|
757
|
-
const llmsResponse = await fetch(llmsUrl);
|
|
758
|
-
if (llmsResponse.ok) return { content: [{
|
|
759
|
-
type: "text",
|
|
760
|
-
text: await llmsResponse.text()
|
|
761
746
|
}] };
|
|
762
|
-
} catch (_) {}
|
|
763
|
-
return {
|
|
764
|
-
isError: true,
|
|
765
|
-
errorMessage: `There was an error fetching documentation for ${name}. Ensure the component exists.`,
|
|
766
|
-
content: []
|
|
767
|
-
};
|
|
768
|
-
});
|
|
769
|
-
server.registerTool("get_component_batch", {
|
|
770
|
-
description: "Retrieve official documentation and usage details for 2 to 10 React components from the @primer/react package in one call. Use get_component for exactly one component.",
|
|
771
|
-
inputSchema: { names: z.array(z.string()).min(2).max(10).describe("Component names to retrieve (e.g. [\"ActionMenu\", \"Button\", \"Pagination\"])") },
|
|
772
|
-
annotations: { readOnlyHint: true }
|
|
773
|
-
}, async ({ names }) => {
|
|
774
|
-
const seenNames = /* @__PURE__ */ new Set();
|
|
775
|
-
const deduped = names.filter((name) => {
|
|
776
|
-
const normalizedName = name.toLowerCase();
|
|
777
|
-
if (seenNames.has(normalizedName)) return false;
|
|
778
|
-
seenNames.add(normalizedName);
|
|
779
|
-
return true;
|
|
780
747
|
});
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
748
|
+
server.registerTool("get_component", {
|
|
749
|
+
description: "Retrieve official documentation and usage details for exactly one React component from the @primer/react package. Use get_component_batch for 2 to 10 components.",
|
|
750
|
+
inputSchema: z.object({ name: z.string().describe("The name of the component to retrieve") }),
|
|
751
|
+
annotations: { readOnlyHint: true }
|
|
752
|
+
}, async ({ name }) => {
|
|
753
|
+
const match = listComponents().find((component) => {
|
|
754
|
+
return component.name === name || component.name.toLowerCase() === name.toLowerCase();
|
|
755
|
+
});
|
|
784
756
|
if (!match) return {
|
|
785
|
-
|
|
786
|
-
|
|
757
|
+
isError: true,
|
|
758
|
+
errorMessage: `There is no component named \`${name}\` in the @primer/react package. For a full list of components, use the \`list_components\` tool.`,
|
|
759
|
+
content: []
|
|
787
760
|
};
|
|
788
|
-
const controller = new AbortController();
|
|
789
|
-
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
790
761
|
try {
|
|
791
762
|
const llmsUrl = new URL(`/product/components/${match.slug}/llms.txt`, "https://primer.style");
|
|
792
|
-
const llmsResponse = await fetch(llmsUrl
|
|
793
|
-
if (llmsResponse.ok) return {
|
|
763
|
+
const llmsResponse = await fetch(llmsUrl);
|
|
764
|
+
if (llmsResponse.ok) return { content: [{
|
|
794
765
|
type: "text",
|
|
795
766
|
text: await llmsResponse.text()
|
|
796
|
-
};
|
|
797
|
-
} catch (_) {}
|
|
798
|
-
clearTimeout(timeout);
|
|
799
|
-
}
|
|
767
|
+
}] };
|
|
768
|
+
} catch (_) {}
|
|
800
769
|
return {
|
|
801
|
-
|
|
802
|
-
|
|
770
|
+
isError: true,
|
|
771
|
+
errorMessage: `There was an error fetching documentation for ${name}. Ensure the component exists.`,
|
|
772
|
+
content: []
|
|
803
773
|
};
|
|
804
|
-
})) };
|
|
805
|
-
});
|
|
806
|
-
server.registerTool("get_component_examples", {
|
|
807
|
-
description: "Get examples for how to use a component from Primer React",
|
|
808
|
-
inputSchema: { name: z.string().describe("The name of the component to retrieve") },
|
|
809
|
-
annotations: { readOnlyHint: true }
|
|
810
|
-
}, async ({ name }) => {
|
|
811
|
-
const match = listComponents().find((component) => {
|
|
812
|
-
return component.name === name;
|
|
813
774
|
});
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
775
|
+
server.registerTool("get_component_batch", {
|
|
776
|
+
description: "Retrieve official documentation and usage details for 2 to 10 React components from the @primer/react package in one call. Use get_component for exactly one component.",
|
|
777
|
+
inputSchema: z.object({ names: z.array(z.string()).min(2).max(10).describe("Component names to retrieve (e.g. [\"ActionMenu\", \"Button\", \"Pagination\"])") }),
|
|
778
|
+
annotations: { readOnlyHint: true }
|
|
779
|
+
}, async ({ names }) => {
|
|
780
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
781
|
+
const deduped = names.filter((name) => {
|
|
782
|
+
const normalizedName = name.toLowerCase();
|
|
783
|
+
if (seenNames.has(normalizedName)) return false;
|
|
784
|
+
seenNames.add(normalizedName);
|
|
785
|
+
return true;
|
|
786
|
+
});
|
|
787
|
+
const components = listComponents();
|
|
788
|
+
return { content: await Promise.all(deduped.map(async (name) => {
|
|
789
|
+
const match = components.find((component) => component.name === name || component.name.toLowerCase() === name.toLowerCase());
|
|
790
|
+
if (!match) return {
|
|
791
|
+
type: "text",
|
|
792
|
+
text: `## ${name}\n\nStatus: not-found. No component named \`${name}\` exists in @primer/react. Use \`list_components\` for valid names.`
|
|
793
|
+
};
|
|
794
|
+
const controller = new AbortController();
|
|
795
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
796
|
+
try {
|
|
797
|
+
const llmsUrl = new URL(`/product/components/${match.slug}/llms.txt`, "https://primer.style");
|
|
798
|
+
const llmsResponse = await fetch(llmsUrl, { signal: controller.signal });
|
|
799
|
+
if (llmsResponse.ok) return {
|
|
800
|
+
type: "text",
|
|
801
|
+
text: await llmsResponse.text()
|
|
802
|
+
};
|
|
803
|
+
} catch (_) {} finally {
|
|
804
|
+
clearTimeout(timeout);
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
type: "text",
|
|
808
|
+
text: `## ${match.name}\n\nStatus: fetch-error. Failed to load documentation for ${match.name}.`
|
|
809
|
+
};
|
|
810
|
+
})) };
|
|
811
|
+
});
|
|
812
|
+
server.registerTool("get_component_examples", {
|
|
813
|
+
description: "Get examples for how to use a component from Primer React",
|
|
814
|
+
inputSchema: z.object({ name: z.string().describe("The name of the component to retrieve") }),
|
|
815
|
+
annotations: { readOnlyHint: true }
|
|
816
|
+
}, async ({ name }) => {
|
|
817
|
+
const match = listComponents().find((component) => {
|
|
818
|
+
return component.name === name;
|
|
819
|
+
});
|
|
820
|
+
if (!match) return { content: [{
|
|
821
|
+
type: "text",
|
|
822
|
+
text: `There is no component named \`${name}\` in the @primer/react package. For a full list of components, use the \`list_components\` tool.`
|
|
823
|
+
}] };
|
|
824
|
+
const url = new URL(`/product/components/${match.id}`, "https://primer.style");
|
|
825
|
+
const response = await fetch(url);
|
|
826
|
+
if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
|
|
827
|
+
const html = await response.text();
|
|
828
|
+
if (!html) return { content: [] };
|
|
829
|
+
const source = cheerio.load(html)("main").html();
|
|
830
|
+
if (!source) return { content: [] };
|
|
831
|
+
return { content: [{
|
|
832
|
+
type: "text",
|
|
833
|
+
text: `Here are some examples of how to use the \`${name}\` component from the @primer/react package:
|
|
828
834
|
|
|
829
835
|
${turndownService.turndown(source)}`
|
|
830
|
-
|
|
831
|
-
});
|
|
832
|
-
server.registerTool("get_component_usage_guidelines", {
|
|
833
|
-
description: "Get usage information for how to use a component from Primer",
|
|
834
|
-
inputSchema: { name: z.string().describe("The name of the component to retrieve") },
|
|
835
|
-
annotations: { readOnlyHint: true }
|
|
836
|
-
}, async ({ name }) => {
|
|
837
|
-
const match = listComponents().find((component) => {
|
|
838
|
-
return component.name === name;
|
|
836
|
+
}] };
|
|
839
837
|
});
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
838
|
+
server.registerTool("get_component_usage_guidelines", {
|
|
839
|
+
description: "Get usage information for how to use a component from Primer",
|
|
840
|
+
inputSchema: z.object({ name: z.string().describe("The name of the component to retrieve") }),
|
|
841
|
+
annotations: { readOnlyHint: true }
|
|
842
|
+
}, async ({ name }) => {
|
|
843
|
+
const match = listComponents().find((component) => {
|
|
844
|
+
return component.name === name;
|
|
845
|
+
});
|
|
846
|
+
if (!match) return { content: [{
|
|
848
847
|
type: "text",
|
|
849
|
-
text: `There
|
|
848
|
+
text: `There is no component named \`${name}\` in the @primer/react package. For a full list of components, use the \`get_components\` tool.`
|
|
850
849
|
}] };
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
850
|
+
const url = new URL(`/product/components/${match.id}/guidelines`, "https://primer.style");
|
|
851
|
+
const response = await fetch(url);
|
|
852
|
+
if (!response.ok) {
|
|
853
|
+
if (response.status >= 400 && response.status < 500 || response.status >= 300 && response.status < 400) return { content: [{
|
|
854
|
+
type: "text",
|
|
855
|
+
text: `There are no accessibility guidelines for the \`${name}\` component in the @primer/react package.`
|
|
856
|
+
}] };
|
|
857
|
+
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
|
|
858
|
+
}
|
|
859
|
+
const html = await response.text();
|
|
860
|
+
if (!html) return { content: [] };
|
|
861
|
+
const source = cheerio.load(html)("main").html();
|
|
862
|
+
if (!source) return { content: [] };
|
|
863
|
+
return { content: [{
|
|
864
|
+
type: "text",
|
|
865
|
+
text: `Here are the usage guidelines for the \`${name}\` component from the @primer/react package:
|
|
860
866
|
|
|
861
867
|
${turndownService.turndown(source)}`
|
|
862
|
-
|
|
863
|
-
});
|
|
864
|
-
server.registerTool("get_component_accessibility_guidelines", {
|
|
865
|
-
description: "Retrieve accessibility guidelines and best practices for a specific component from the @primer/react package by its name. Use this tool to get official accessibility recommendations, usage tips, and requirements to ensure your UI components are inclusive and meet accessibility standards.",
|
|
866
|
-
inputSchema: { name: z.string().describe("The name of the component to retrieve") },
|
|
867
|
-
annotations: { readOnlyHint: true }
|
|
868
|
-
}, async ({ name }) => {
|
|
869
|
-
const match = listComponents().find((component) => {
|
|
870
|
-
return component.name === name;
|
|
868
|
+
}] };
|
|
871
869
|
});
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
870
|
+
server.registerTool("get_component_accessibility_guidelines", {
|
|
871
|
+
description: "Retrieve accessibility guidelines and best practices for a specific component from the @primer/react package by its name. Use this tool to get official accessibility recommendations, usage tips, and requirements to ensure your UI components are inclusive and meet accessibility standards.",
|
|
872
|
+
inputSchema: z.object({ name: z.string().describe("The name of the component to retrieve") }),
|
|
873
|
+
annotations: { readOnlyHint: true }
|
|
874
|
+
}, async ({ name }) => {
|
|
875
|
+
const match = listComponents().find((component) => {
|
|
876
|
+
return component.name === name;
|
|
877
|
+
});
|
|
878
|
+
if (!match) return { content: [{
|
|
880
879
|
type: "text",
|
|
881
|
-
text: `There
|
|
880
|
+
text: `There is no component named \`${name}\` in the @primer/react package. For a full list of components, use the \`list_components\` tool.`
|
|
882
881
|
}] };
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
882
|
+
const url = new URL(`/product/components/${match.id}/accessibility`, "https://primer.style");
|
|
883
|
+
const response = await fetch(url);
|
|
884
|
+
if (!response.ok) {
|
|
885
|
+
if (response.status >= 400 && response.status < 500 || response.status >= 300 && response.status < 400) return { content: [{
|
|
886
|
+
type: "text",
|
|
887
|
+
text: `There are no accessibility guidelines for the \`${name}\` component in the @primer/react package.`
|
|
888
|
+
}] };
|
|
889
|
+
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
|
|
890
|
+
}
|
|
891
|
+
const html = await response.text();
|
|
892
|
+
if (!html) return { content: [] };
|
|
893
|
+
const source = cheerio.load(html)("main").html();
|
|
894
|
+
if (!source) return { content: [] };
|
|
895
|
+
return { content: [{
|
|
896
|
+
type: "text",
|
|
897
|
+
text: `Here are the accessibility guidelines for the \`${name}\` component from the @primer/react package:
|
|
892
898
|
|
|
893
899
|
${turndownService.turndown(source)}`
|
|
894
|
-
|
|
895
|
-
});
|
|
896
|
-
server.registerTool("list_patterns", {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
}, async () => {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
900
|
+
}] };
|
|
901
|
+
});
|
|
902
|
+
server.registerTool("list_patterns", {
|
|
903
|
+
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.",
|
|
904
|
+
annotations: { readOnlyHint: true }
|
|
905
|
+
}, async () => {
|
|
906
|
+
const all = listPatterns();
|
|
907
|
+
const scenario = all.filter((pattern) => pattern.category === "scenario").map((pattern) => `- ${pattern.name}`);
|
|
908
|
+
const ui = all.filter((pattern) => pattern.category === "ui").map((pattern) => `- ${pattern.name}`);
|
|
909
|
+
return { content: [{
|
|
910
|
+
type: "text",
|
|
911
|
+
text: `The following patterns are available from \`@primer/react\` for use in TypeScript projects. Scenario patterns describe specific user tasks. Prefer a scenario pattern when one fits the task, and fall back to the UI patterns otherwise.
|
|
906
912
|
|
|
907
913
|
## Scenario patterns
|
|
908
914
|
|
|
@@ -911,65 +917,66 @@ ${scenario.join("\n")}
|
|
|
911
917
|
## UI patterns
|
|
912
918
|
|
|
913
919
|
${ui.join("\n")}`
|
|
914
|
-
|
|
915
|
-
});
|
|
916
|
-
server.registerTool("get_pattern", {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}, async ({ name }) => {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
920
|
+
}] };
|
|
921
|
+
});
|
|
922
|
+
server.registerTool("get_pattern", {
|
|
923
|
+
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.",
|
|
924
|
+
inputSchema: z.object({ name: z.string().describe("The name of the pattern to retrieve") }),
|
|
925
|
+
annotations: { readOnlyHint: true }
|
|
926
|
+
}, async ({ name }) => {
|
|
927
|
+
const patterns = listPatterns();
|
|
928
|
+
const match = patterns.find((pattern) => pattern.category === "scenario" && pattern.name === name) ?? patterns.find((pattern) => pattern.name === name);
|
|
929
|
+
if (!match) return { content: [{
|
|
930
|
+
type: "text",
|
|
931
|
+
text: `There is no pattern named \`${name}\` in the @primer/react package. For a full list of patterns, use the \`list_patterns\` tool.`
|
|
932
|
+
}] };
|
|
933
|
+
const basePath = match.category === "scenario" ? "scenario-patterns" : "ui-patterns";
|
|
934
|
+
const url = new URL(`/product/${basePath}/${match.id}`, "https://primer.style");
|
|
935
|
+
const response = await fetch(url);
|
|
936
|
+
if (!response.ok) throw new Error(`Failed to fetch ${url} - ${response.statusText}`);
|
|
937
|
+
const html = await response.text();
|
|
938
|
+
if (!html) return { content: [] };
|
|
939
|
+
const source = cheerio.load(html)("main").html();
|
|
940
|
+
if (!source) return { content: [] };
|
|
941
|
+
return { content: [{
|
|
942
|
+
type: "text",
|
|
943
|
+
text: `Here are the guidelines for the \`${name}\` pattern for Primer:
|
|
938
944
|
|
|
939
945
|
${turndownService.turndown(source)}`
|
|
940
|
-
|
|
941
|
-
});
|
|
942
|
-
server.registerTool("find_tokens", {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
}, async ({ query, group, limit }) => {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
text: "Please provide a query, a group, or both. Call `get_design_token_specs` to see available token groups."
|
|
963
|
-
}] };
|
|
964
|
-
const isGroupOnly = filteredKeywords.length === 0 && effectiveGroup;
|
|
965
|
-
let results;
|
|
966
|
-
if (isGroupOnly) results = allTokensWithGuidelines.filter((token) => tokenMatchesGroup(token, effectiveGroup));
|
|
967
|
-
else results = searchTokens(allTokensWithGuidelines, filteredKeywords.join(" "), effectiveGroup);
|
|
968
|
-
if (results.length === 0) {
|
|
969
|
-
const validGroups = getValidGroupsList(allTokensWithGuidelines);
|
|
970
|
-
return { content: [{
|
|
946
|
+
}] };
|
|
947
|
+
});
|
|
948
|
+
server.registerTool("find_tokens", {
|
|
949
|
+
description: "Search for specific tokens. Tip: If you only provide a 'group' and leave 'query' empty, it returns all tokens in that category. Avoid property-by-property searching. COLOR RESOLUTION: If a user asks for \"pink\" or \"blue\", do not search for the color name. Use the semantic intent: blue->accent, red->danger, green->success. Always check both \"emphasis\" and \"muted\" variants for background colors. After identifying tokens and writing CSS, you MUST validate the result using lint_css.",
|
|
950
|
+
inputSchema: z.object({
|
|
951
|
+
query: z.string().optional().default("").describe("Search keywords (e.g., \"danger border\", \"success background\")"),
|
|
952
|
+
group: z.string().optional().describe("Filter by group (e.g., \"fgColor\", \"border\")"),
|
|
953
|
+
limit: z.number().int().min(1).max(100).optional().default(15).describe("Maximum results to return to stay within context limits")
|
|
954
|
+
}),
|
|
955
|
+
annotations: { readOnlyHint: true }
|
|
956
|
+
}, async ({ query, group, limit }) => {
|
|
957
|
+
const resolvedGroup = group ? GROUP_ALIASES[group.toLowerCase().replace(/\s+/g, "")] || group : void 0;
|
|
958
|
+
const rawKeywords = query.toLowerCase().split(/\s+/).filter((k) => k.length > 0);
|
|
959
|
+
let effectiveGroup = resolvedGroup;
|
|
960
|
+
const filteredKeywords = [];
|
|
961
|
+
for (const kw of rawKeywords) {
|
|
962
|
+
const normalized = kw.replace(/\s+/g, "");
|
|
963
|
+
const aliasMatch = GROUP_ALIASES[normalized];
|
|
964
|
+
if (aliasMatch && !effectiveGroup) effectiveGroup = aliasMatch;
|
|
965
|
+
else filteredKeywords.push(kw);
|
|
966
|
+
}
|
|
967
|
+
if (filteredKeywords.length === 0 && !effectiveGroup) return { content: [{
|
|
971
968
|
type: "text",
|
|
972
|
-
text:
|
|
969
|
+
text: "Please provide a query, a group, or both. Call `get_design_token_specs` to see available token groups."
|
|
970
|
+
}] };
|
|
971
|
+
const isGroupOnly = filteredKeywords.length === 0 && effectiveGroup;
|
|
972
|
+
let results;
|
|
973
|
+
if (isGroupOnly) results = allTokensWithGuidelines.filter((token) => tokenMatchesGroup(token, effectiveGroup));
|
|
974
|
+
else results = searchTokens(allTokensWithGuidelines, filteredKeywords.join(" "), effectiveGroup);
|
|
975
|
+
if (results.length === 0) {
|
|
976
|
+
const validGroups = getValidGroupsList(allTokensWithGuidelines);
|
|
977
|
+
return { content: [{
|
|
978
|
+
type: "text",
|
|
979
|
+
text: `No tokens found matching "${query}"${effectiveGroup ? ` in group "${effectiveGroup}"` : ""}.
|
|
973
980
|
|
|
974
981
|
### 💡 Available Groups:
|
|
975
982
|
${validGroups}
|
|
@@ -979,188 +986,189 @@ ${validGroups}
|
|
|
979
986
|
2. **Property Mismatch**: Do not search for CSS properties like "offset", "padding", or "font-size". Use semantic intent keywords: "danger", "muted", "emphasis".
|
|
980
987
|
3. **Typography**: Remember that \`caption\`, \`display\`, and \`code\` groups do NOT support size suffixes. Use the base shorthand only.
|
|
981
988
|
4. **Group Intent**: Use the \`group\` parameter instead of putting group names in the \`query\` string (e.g., use group: "stack" instead of query: "stack padding").`
|
|
989
|
+
}] };
|
|
990
|
+
}
|
|
991
|
+
const limitedResults = results.slice(0, limit);
|
|
992
|
+
let output;
|
|
993
|
+
if (!query) output = `Found ${results.length} token(s). Showing top ${limitedResults.length}:\n\n`;
|
|
994
|
+
else output = `Found ${results.length} token(s) matching "${query}". Showing top ${limitedResults.length}:\n\n`;
|
|
995
|
+
output += formatBundle(limitedResults);
|
|
996
|
+
if (results.length > limit) output += `\n\n*...and ${results.length - limit} more matches. Use more specific keywords to narrow the search.*`;
|
|
997
|
+
return { content: [{
|
|
998
|
+
type: "text",
|
|
999
|
+
text: output
|
|
982
1000
|
}] };
|
|
983
|
-
}
|
|
984
|
-
const limitedResults = results.slice(0, limit);
|
|
985
|
-
let output;
|
|
986
|
-
if (!query) output = `Found ${results.length} token(s). Showing top ${limitedResults.length}:\n\n`;
|
|
987
|
-
else output = `Found ${results.length} token(s) matching "${query}". Showing top ${limitedResults.length}:\n\n`;
|
|
988
|
-
output += formatBundle(limitedResults);
|
|
989
|
-
if (results.length > limit) output += `\n\n*...and ${results.length - limit} more matches. Use more specific keywords to narrow the search.*`;
|
|
990
|
-
return { content: [{
|
|
991
|
-
type: "text",
|
|
992
|
-
text: output
|
|
993
|
-
}] };
|
|
994
|
-
});
|
|
995
|
-
server.registerTool("get_token_group_bundle", {
|
|
996
|
-
description: "PREFERRED FOR COMPONENTS. Fetch all tokens for complex UI (e.g., Dialogs, Cards) in one call by providing an array of groups like ['overlay', 'shadow']. Use this instead of multiple find_tokens calls to save context.",
|
|
997
|
-
inputSchema: { groups: z.array(z.string()).describe("Array of group names (e.g., [\"overlay\", \"shadow\", \"focus\"])") },
|
|
998
|
-
annotations: { readOnlyHint: true }
|
|
999
|
-
}, async ({ groups }) => {
|
|
1000
|
-
const resolvedGroups = groups.map((g) => {
|
|
1001
|
-
return GROUP_ALIASES[g.toLowerCase().replace(/\s+/g, "")] || g;
|
|
1002
1001
|
});
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1002
|
+
server.registerTool("get_token_group_bundle", {
|
|
1003
|
+
description: "PREFERRED FOR COMPONENTS. Fetch all tokens for complex UI (e.g., Dialogs, Cards) in one call by providing an array of groups like ['overlay', 'shadow']. Use this instead of multiple find_tokens calls to save context.",
|
|
1004
|
+
inputSchema: z.object({ groups: z.array(z.string()).describe("Array of group names (e.g., [\"overlay\", \"shadow\", \"focus\"])") }),
|
|
1005
|
+
annotations: { readOnlyHint: true }
|
|
1006
|
+
}, async ({ groups }) => {
|
|
1007
|
+
const resolvedGroups = groups.map((g) => {
|
|
1008
|
+
const normalized = g.toLowerCase().replace(/\s+/g, "");
|
|
1009
|
+
return GROUP_ALIASES[normalized] || g;
|
|
1010
|
+
});
|
|
1011
|
+
const matched = allTokensWithGuidelines.filter((token) => resolvedGroups.some((rg) => tokenMatchesGroup(token, rg)));
|
|
1012
|
+
if (matched.length === 0) {
|
|
1013
|
+
const validGroups = getValidGroupsList(allTokensWithGuidelines);
|
|
1014
|
+
return { content: [{
|
|
1015
|
+
type: "text",
|
|
1016
|
+
text: `No tokens found for groups: ${groups.join(", ")}.\n\n### Valid Groups:\n${validGroups}`
|
|
1017
|
+
}] };
|
|
1018
|
+
}
|
|
1019
|
+
let text = `Found ${matched.length} token(s) across ${resolvedGroups.length} group(s):\n\n${formatBundle(matched)}`;
|
|
1020
|
+
const activeHints = resolvedGroups.map((g) => groupHints[g]).filter(Boolean);
|
|
1021
|
+
if (activeHints.length > 0) text += `\n\n### ⚠️ Usage Guidance:\n${activeHints.map((h) => `- ${h}`).join("\n")}`;
|
|
1006
1022
|
return { content: [{
|
|
1007
1023
|
type: "text",
|
|
1008
|
-
text
|
|
1024
|
+
text
|
|
1009
1025
|
}] };
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
text
|
|
1017
|
-
|
|
1018
|
-
})
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
}, async () => {
|
|
1023
|
-
const customRules = getDesignTokenSpecsText(listTokenGroups());
|
|
1024
|
-
let text;
|
|
1025
|
-
try {
|
|
1026
|
-
text = `${customRules}\n\n---\n\n${loadDesignTokensGuide()}`;
|
|
1027
|
-
} catch {
|
|
1028
|
-
text = customRules;
|
|
1029
|
-
}
|
|
1030
|
-
return { content: [{
|
|
1031
|
-
type: "text",
|
|
1032
|
-
text
|
|
1033
|
-
}] };
|
|
1034
|
-
});
|
|
1035
|
-
server.registerTool("get_token_usage_patterns", {
|
|
1036
|
-
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.",
|
|
1037
|
-
annotations: { readOnlyHint: true }
|
|
1038
|
-
}, async () => {
|
|
1039
|
-
const customPatterns = getTokenUsagePatternsText();
|
|
1040
|
-
let text;
|
|
1041
|
-
try {
|
|
1042
|
-
const goldenExampleMatch = loadDesignTokensGuide().match(/## Golden Example[\s\S]*?(?=\n## |$)/);
|
|
1043
|
-
if (goldenExampleMatch) text = `${customPatterns}\n\n---\n\n${goldenExampleMatch[0].trim()}`;
|
|
1044
|
-
else text = customPatterns;
|
|
1045
|
-
} catch {
|
|
1046
|
-
text = customPatterns;
|
|
1047
|
-
}
|
|
1048
|
-
return { content: [{
|
|
1049
|
-
type: "text",
|
|
1050
|
-
text
|
|
1051
|
-
}] };
|
|
1052
|
-
});
|
|
1053
|
-
server.registerTool("lint_css", {
|
|
1054
|
-
description: "REQUIRED FINAL STEP. Use this to validate your CSS. You cannot complete a task involving CSS without a successful run of this tool.",
|
|
1055
|
-
inputSchema: { css: z.string() },
|
|
1056
|
-
annotations: { readOnlyHint: true }
|
|
1057
|
-
}, async ({ css }) => {
|
|
1058
|
-
try {
|
|
1059
|
-
const { stdout } = await runStylelint(css);
|
|
1026
|
+
});
|
|
1027
|
+
server.registerTool("get_design_token_specs", {
|
|
1028
|
+
description: "CRITICAL: CALL THIS FIRST. Provides the logic matrix and the list of valid group names. You cannot search accurately without this map.",
|
|
1029
|
+
annotations: { readOnlyHint: true }
|
|
1030
|
+
}, async () => {
|
|
1031
|
+
const customRules = getDesignTokenSpecsText(listTokenGroups());
|
|
1032
|
+
let text;
|
|
1033
|
+
try {
|
|
1034
|
+
text = `${customRules}\n\n---\n\n${loadDesignTokensGuide()}`;
|
|
1035
|
+
} catch {
|
|
1036
|
+
text = customRules;
|
|
1037
|
+
}
|
|
1060
1038
|
return { content: [{
|
|
1061
1039
|
type: "text",
|
|
1062
|
-
text
|
|
1040
|
+
text
|
|
1063
1041
|
}] };
|
|
1064
|
-
}
|
|
1042
|
+
});
|
|
1043
|
+
server.registerTool("get_token_usage_patterns", {
|
|
1044
|
+
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.",
|
|
1045
|
+
annotations: { readOnlyHint: true }
|
|
1046
|
+
}, async () => {
|
|
1047
|
+
const customPatterns = getTokenUsagePatternsText();
|
|
1048
|
+
let text;
|
|
1049
|
+
try {
|
|
1050
|
+
const goldenExampleMatch = loadDesignTokensGuide().match(/## Golden Example[\s\S]*?(?=\n## |$)/);
|
|
1051
|
+
if (goldenExampleMatch) text = `${customPatterns}\n\n---\n\n${goldenExampleMatch[0].trim()}`;
|
|
1052
|
+
else text = customPatterns;
|
|
1053
|
+
} catch {
|
|
1054
|
+
text = customPatterns;
|
|
1055
|
+
}
|
|
1065
1056
|
return { content: [{
|
|
1066
1057
|
type: "text",
|
|
1067
|
-
text
|
|
1058
|
+
text
|
|
1068
1059
|
}] };
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
}, async () => {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1060
|
+
});
|
|
1061
|
+
server.registerTool("lint_css", {
|
|
1062
|
+
description: "REQUIRED FINAL STEP. Use this to validate your CSS. You cannot complete a task involving CSS without a successful run of this tool.",
|
|
1063
|
+
inputSchema: z.object({ css: z.string() }),
|
|
1064
|
+
annotations: { readOnlyHint: true }
|
|
1065
|
+
}, async ({ css }) => {
|
|
1066
|
+
try {
|
|
1067
|
+
const { stdout } = await runStylelint(css);
|
|
1068
|
+
return { content: [{
|
|
1069
|
+
type: "text",
|
|
1070
|
+
text: stdout || "✅ Stylelint passed (or was successfully autofixed)."
|
|
1071
|
+
}] };
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
return { content: [{
|
|
1074
|
+
type: "text",
|
|
1075
|
+
text: `❌ Errors without autofix remaining:\n${error instanceof Error && "stdout" in error ? error.stdout : String(error)}`
|
|
1076
|
+
}] };
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
server.registerTool("get_color_usage", {
|
|
1080
|
+
description: "Get the guidelines for how to apply color to a user interface",
|
|
1081
|
+
annotations: { readOnlyHint: true }
|
|
1082
|
+
}, async () => {
|
|
1083
|
+
const url = new URL(`/product/getting-started/foundations/color-usage`, "https://primer.style");
|
|
1084
|
+
const response = await fetch(url);
|
|
1085
|
+
if (!response.ok) throw new Error(`Failed to fetch ${url} - ${response.statusText}`);
|
|
1086
|
+
const html = await response.text();
|
|
1087
|
+
if (!html) return { content: [] };
|
|
1088
|
+
const source = cheerio.load(html)("main").html();
|
|
1089
|
+
if (!source) return { content: [] };
|
|
1090
|
+
return { content: [{
|
|
1091
|
+
type: "text",
|
|
1092
|
+
text: `Here is the documentation for color usage in Primer:\n\n${turndownService.turndown(source)}`
|
|
1093
|
+
}] };
|
|
1094
|
+
});
|
|
1095
|
+
server.registerTool("get_typography_usage", {
|
|
1096
|
+
description: "Get the guidelines for how to apply typography to a user interface",
|
|
1097
|
+
annotations: { readOnlyHint: true }
|
|
1098
|
+
}, async () => {
|
|
1099
|
+
const url = new URL(`/product/getting-started/foundations/typography`, "https://primer.style");
|
|
1100
|
+
const response = await fetch(url);
|
|
1101
|
+
if (!response.ok) throw new Error(`Failed to fetch ${url} - ${response.statusText}`);
|
|
1102
|
+
const html = await response.text();
|
|
1103
|
+
if (!html) return { content: [] };
|
|
1104
|
+
const source = cheerio.load(html)("main").html();
|
|
1105
|
+
if (!source) return { content: [] };
|
|
1106
|
+
return { content: [{
|
|
1107
|
+
type: "text",
|
|
1108
|
+
text: `Here is the documentation for typography usage in Primer:\n\n${turndownService.turndown(source)}`
|
|
1109
|
+
}] };
|
|
1110
|
+
});
|
|
1111
|
+
server.registerTool("list_icons", {
|
|
1112
|
+
description: "List all of the icons (octicons) available from Primer Octicons React",
|
|
1113
|
+
annotations: { readOnlyHint: true }
|
|
1114
|
+
}, async () => {
|
|
1115
|
+
return { content: [{
|
|
1116
|
+
type: "text",
|
|
1117
|
+
text: `The following icons are available in the @primer/octicons-react package in TypeScript projects:
|
|
1110
1118
|
|
|
1111
1119
|
${listIcons().map((icon) => {
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1120
|
+
const keywords = icon.keywords.map((keyword) => {
|
|
1121
|
+
return `<keyword>${keyword}</keyword>`;
|
|
1122
|
+
});
|
|
1123
|
+
const sizes = icon.heights.map((height) => {
|
|
1124
|
+
return `<size value="${height}"></size>`;
|
|
1125
|
+
});
|
|
1126
|
+
return [
|
|
1127
|
+
`<icon name="${icon.name}">`,
|
|
1128
|
+
...keywords,
|
|
1129
|
+
...sizes,
|
|
1130
|
+
`</icon>`
|
|
1131
|
+
].join("\n");
|
|
1132
|
+
}).join("\n")}
|
|
1125
1133
|
|
|
1126
1134
|
You can use the \`get_icon\` tool to get more information about a specific icon. You can use these components from the @primer/octicons-react package.`
|
|
1127
|
-
|
|
1128
|
-
});
|
|
1129
|
-
server.registerTool("get_icon", {
|
|
1130
|
-
description: "Get a specific icon (octicon) by name from Primer",
|
|
1131
|
-
inputSchema: {
|
|
1132
|
-
name: z.string().describe("The name of the icon to retrieve"),
|
|
1133
|
-
size: z.string().optional().describe("The size of the icon to retrieve, e.g. \"16\"").default("16")
|
|
1134
|
-
},
|
|
1135
|
-
annotations: { readOnlyHint: true }
|
|
1136
|
-
}, async ({ name, size }) => {
|
|
1137
|
-
const match = listIcons().find((icon) => {
|
|
1138
|
-
return icon.name === name || icon.name.toLowerCase() === name.toLowerCase();
|
|
1135
|
+
}] };
|
|
1139
1136
|
});
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1137
|
+
server.registerTool("get_icon", {
|
|
1138
|
+
description: "Get a specific icon (octicon) by name from Primer",
|
|
1139
|
+
inputSchema: z.object({
|
|
1140
|
+
name: z.string().describe("The name of the icon to retrieve"),
|
|
1141
|
+
size: z.string().optional().describe("The size of the icon to retrieve, e.g. \"16\"").default("16")
|
|
1142
|
+
}),
|
|
1143
|
+
annotations: { readOnlyHint: true }
|
|
1144
|
+
}, async ({ name, size }) => {
|
|
1145
|
+
const match = listIcons().find((icon) => {
|
|
1146
|
+
return icon.name === name || icon.name.toLowerCase() === name.toLowerCase();
|
|
1147
|
+
});
|
|
1148
|
+
if (!match) return { content: [{
|
|
1149
|
+
type: "text",
|
|
1150
|
+
text: `There is no icon named \`${name}\` in the @primer/octicons-react package. For a full list of icons, use the \`get_icon\` tool.`
|
|
1151
|
+
}] };
|
|
1152
|
+
const url = new URL(`/octicons/icon/${match.name}-${size}`, "https://primer.style");
|
|
1153
|
+
const response = await fetch(url);
|
|
1154
|
+
if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
|
|
1155
|
+
const html = await response.text();
|
|
1156
|
+
if (!html) return { content: [] };
|
|
1157
|
+
const source = cheerio.load(html)("main").html();
|
|
1158
|
+
if (!source) return { content: [] };
|
|
1159
|
+
return { content: [{
|
|
1160
|
+
type: "text",
|
|
1161
|
+
text: `Here is the documentation for the \`${name}\` icon at size: \`${size}\`:
|
|
1154
1162
|
${turndownService.turndown(source)}`
|
|
1155
|
-
|
|
1156
|
-
});
|
|
1157
|
-
server.registerTool("primer_coding_guidelines", {
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
}, async () => {
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1163
|
+
}] };
|
|
1164
|
+
});
|
|
1165
|
+
server.registerTool("primer_coding_guidelines", {
|
|
1166
|
+
description: "Get the guidelines when writing code that uses Primer or for UI code that you are creating",
|
|
1167
|
+
annotations: { readOnlyHint: true }
|
|
1168
|
+
}, async () => {
|
|
1169
|
+
return { content: [{
|
|
1170
|
+
type: "text",
|
|
1171
|
+
text: `When writing code that uses Primer, follow these guidelines:
|
|
1164
1172
|
|
|
1165
1173
|
## Design Tokens
|
|
1166
1174
|
|
|
@@ -1182,46 +1190,51 @@ The following list of coding guidelines must be followed:
|
|
|
1182
1190
|
- Do not use the sx prop for styling components. Instead, use CSS Modules.
|
|
1183
1191
|
- Do not use the Box component for styling components. Instead, use CSS Modules.
|
|
1184
1192
|
`
|
|
1185
|
-
|
|
1186
|
-
});
|
|
1187
|
-
/**
|
|
1188
|
-
* The `review_alt_text` tool is experimental and may be removed in future versions.
|
|
1189
|
-
*
|
|
1190
|
-
* The intent of this tool is to assist products like Copilot Code Review and Copilot Coding Agent
|
|
1191
|
-
* in reviewing both user- and AI-generated alt text for images, ensuring compliance with accessibility guidelines.
|
|
1192
|
-
* This tool is not intended to replace human-generated alt text; rather, it supports the review process
|
|
1193
|
-
* by providing suggestions for improvement. It should be used alongside human review, not as a substitute.
|
|
1194
|
-
*
|
|
1195
|
-
*
|
|
1196
|
-
**/
|
|
1197
|
-
server.registerTool("review_alt_text", {
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
}, async ({ surroundingText, alt, image }) => {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1193
|
+
}] };
|
|
1194
|
+
});
|
|
1195
|
+
/**
|
|
1196
|
+
* The `review_alt_text` tool is experimental and may be removed in future versions.
|
|
1197
|
+
*
|
|
1198
|
+
* The intent of this tool is to assist products like Copilot Code Review and Copilot Coding Agent
|
|
1199
|
+
* in reviewing both user- and AI-generated alt text for images, ensuring compliance with accessibility guidelines.
|
|
1200
|
+
* This tool is not intended to replace human-generated alt text; rather, it supports the review process
|
|
1201
|
+
* by providing suggestions for improvement. It should be used alongside human review, not as a substitute.
|
|
1202
|
+
*
|
|
1203
|
+
*
|
|
1204
|
+
**/
|
|
1205
|
+
server.registerTool("review_alt_text", {
|
|
1206
|
+
description: "Evaluates image alt text against accessibility best practices and context relevance.",
|
|
1207
|
+
inputSchema: z.object({
|
|
1208
|
+
surroundingText: z.string().describe("Text surrounding the image, relevant to the image."),
|
|
1209
|
+
alt: z.string().describe("The alt text of the image being evaluated"),
|
|
1210
|
+
image: z.string().describe("The image URL or file path being evaluated")
|
|
1211
|
+
}),
|
|
1212
|
+
annotations: { readOnlyHint: true }
|
|
1213
|
+
}, async ({ surroundingText, alt, image }, context) => {
|
|
1214
|
+
const response = inputResponse(context.mcpReq.inputResponses, "altTextEvaluation");
|
|
1215
|
+
if (response.kind !== "sampling") return inputRequired({ inputRequests: { altTextEvaluation: inputRequired.createMessage({
|
|
1216
|
+
messages: [{
|
|
1217
|
+
role: "user",
|
|
1218
|
+
content: {
|
|
1219
|
+
type: "text",
|
|
1220
|
+
text: `Does this alt text: '${alt}' meet accessibility guidelines and describe the image: ${image} accurately in context of this surrounding text: '${surroundingText}'?\n\n`
|
|
1221
|
+
}
|
|
1222
|
+
}],
|
|
1223
|
+
temperature: .4,
|
|
1224
|
+
maxTokens: 500
|
|
1225
|
+
}) } });
|
|
1226
|
+
const evaluation = (Array.isArray(response.result.content) ? response.result.content : [response.result.content]).filter((block) => block.type === "text").map((block) => block.text).join("\n") || "Unable to generate summary";
|
|
1227
|
+
return {
|
|
1228
|
+
content: [{
|
|
1210
1229
|
type: "text",
|
|
1211
|
-
text:
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1230
|
+
text: evaluation
|
|
1231
|
+
}],
|
|
1232
|
+
altTextEvaluation: evaluation,
|
|
1233
|
+
nextSteps: `If the evaluation indicates issues with the alt text, provide more meaningful alt text based on the feedback. DO NOT run this tool repeatedly on the same image - evaluations may vary slightly with each run.`
|
|
1234
|
+
};
|
|
1216
1235
|
});
|
|
1217
|
-
return
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
text: response.content.type === "text" ? response.content.text : "Unable to generate summary"
|
|
1221
|
-
}],
|
|
1222
|
-
altTextEvaluation: response.content.type === "text" ? response.content.text : "Unable to generate summary",
|
|
1223
|
-
nextSteps: `If the evaluation indicates issues with the alt text, provide more meaningful alt text based on the feedback. DO NOT run this tool repeatedly on the same image - evaluations may vary slightly with each run.`
|
|
1224
|
-
};
|
|
1225
|
-
});
|
|
1236
|
+
return server;
|
|
1237
|
+
}
|
|
1238
|
+
const server = createServer();
|
|
1226
1239
|
//#endregion
|
|
1227
|
-
export { server as t };
|
|
1240
|
+
export { server as n, createServer as t };
|