@primer/mcp 0.5.1 → 1.0.0-rc.f27c5cca1

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