@protoscan/mcp 0.0.1 → 0.0.2

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.
Files changed (2) hide show
  1. package/dist/index.js +74 -68
  2. package/package.json +7 -3
package/dist/index.js CHANGED
@@ -1,21 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import {
7
- CallToolRequestSchema,
8
- ListToolsRequestSchema
9
- } from "@modelcontextprotocol/sdk/types.js";
10
6
  import { z } from "zod";
11
- import { FigmaClient, FigmaApiError, scan, formatTerminal, formatJson } from "@protoscan/core";
12
- var ScanArgsSchema = z.object({
13
- file_key: z.string().describe('Figma file key or full URL (e.g. "abc123" or "https://figma.com/design/abc123/Name?node-id=7-2")'),
14
- token: z.string().optional().describe("Figma Personal Access Token. Falls back to FIGMA_TOKEN env var."),
15
- format: z.enum(["terminal", "json"]).optional().default("terminal").describe("Output format"),
16
- min_touch_target: z.number().optional().default(44).describe("Minimum touch target size in px"),
17
- skip: z.array(z.string()).optional().describe("Checks to skip: dead-end, orphan, back-nav, touch-target, overlap, scroll, overlay-trap")
18
- });
7
+ import { FigmaClient, FigmaApiError, scan, formatTerminal, formatJson, formatHtml } from "@protoscan/core";
8
+ import {
9
+ registerAppTool,
10
+ registerAppResource,
11
+ RESOURCE_MIME_TYPE
12
+ } from "@modelcontextprotocol/ext-apps/server";
19
13
  function parseFigmaInput(input) {
20
14
  const urlMatch = input.match(/figma\.com\/(?:design|file)\/([a-zA-Z0-9]+)/);
21
15
  if (urlMatch) {
@@ -26,66 +20,78 @@ function parseFigmaInput(input) {
26
20
  }
27
21
  return { fileKey: input, pageIds: [] };
28
22
  }
29
- var server = new Server(
23
+ var server = new McpServer(
30
24
  { name: "protoscan", version: "0.0.1" },
31
- { capabilities: { tools: {} } }
25
+ { capabilities: { resources: {} } }
32
26
  );
33
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
34
- tools: [
35
- {
36
- name: "scan_figma_prototype",
37
- description: "Scan a Figma file for prototype navigation issues: dead-end screens, orphan screens, missing back navigation, undersized touch targets, overlapping hotspots, missing scroll, and overlay traps. Returns a detailed report.",
38
- inputSchema: {
39
- type: "object",
40
- properties: {
41
- file_key: { type: "string", description: "Figma file key from the URL" },
42
- token: { type: "string", description: "Figma PAT (optional, falls back to FIGMA_TOKEN env var)" },
43
- format: { type: "string", enum: ["terminal", "json"], default: "terminal" },
44
- min_touch_target: { type: "number", default: 44, description: "Min touch target px" },
45
- skip: { type: "array", items: { type: "string" }, description: "Checks to skip" }
46
- },
47
- required: ["file_key"]
27
+ var latestReportHtml = "<html><body><p>Run a scan first.</p></body></html>";
28
+ registerAppResource(
29
+ server,
30
+ "ProtoScan Report",
31
+ "ui://protoscan/report",
32
+ {
33
+ description: "Interactive ProtoScan scan report with severity filters and Figma deep links"
34
+ },
35
+ async () => ({
36
+ contents: [
37
+ {
38
+ uri: "ui://protoscan/report",
39
+ mimeType: RESOURCE_MIME_TYPE,
40
+ text: latestReportHtml
48
41
  }
42
+ ]
43
+ })
44
+ );
45
+ registerAppTool(
46
+ server,
47
+ "scan_figma_prototype",
48
+ {
49
+ title: "Scan Figma Prototype",
50
+ description: "Scan a Figma file for prototype navigation issues: dead-end screens, orphan screens, missing back navigation, undersized touch targets, overlapping hotspots, missing scroll, and overlay traps. Returns a detailed report. If the client supports MCP Apps, renders an interactive HTML report inline.",
51
+ inputSchema: {
52
+ file_key: z.string().describe('Figma file key or full URL (e.g. "abc123" or "https://figma.com/design/abc123/Name?node-id=7-2")'),
53
+ token: z.string().optional().describe("Figma Personal Access Token. Falls back to FIGMA_TOKEN env var."),
54
+ format: z.enum(["terminal", "json"]).optional().default("terminal").describe("Output format for text response"),
55
+ min_touch_target: z.number().optional().default(44).describe("Minimum touch target size in px"),
56
+ skip: z.array(z.string()).optional().describe("Checks to skip: dead-end, orphan, back-nav, touch-target, overlap, scroll, overlay-trap")
57
+ },
58
+ _meta: {
59
+ ui: { resourceUri: "ui://protoscan/report" }
60
+ }
61
+ },
62
+ async (args) => {
63
+ const token = args.token || process.env.FIGMA_TOKEN;
64
+ if (!token) {
65
+ return {
66
+ content: [{
67
+ type: "text",
68
+ text: 'Error: Figma token required. Pass it as "token" parameter or set FIGMA_TOKEN env var.'
69
+ }]
70
+ };
71
+ }
72
+ try {
73
+ const { fileKey, pageIds } = parseFigmaInput(args.file_key);
74
+ const client = new FigmaClient(token);
75
+ const file = await client.getFile(fileKey);
76
+ const result = await scan(file, {
77
+ fileKey,
78
+ minTouchTarget: args.min_touch_target,
79
+ skip: args.skip,
80
+ pageIds: pageIds.length ? pageIds : void 0
81
+ });
82
+ latestReportHtml = formatHtml(result);
83
+ const output = args.format === "json" ? formatJson(result) : formatTerminal(result);
84
+ return {
85
+ content: [{ type: "text", text: output }]
86
+ };
87
+ } catch (error) {
88
+ const message = error instanceof FigmaApiError ? error.message : error instanceof Error ? error.message : String(error);
89
+ return {
90
+ content: [{ type: "text", text: `Error: ${message}` }]
91
+ };
49
92
  }
50
- ]
51
- }));
52
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
53
- if (request.params.name !== "scan_figma_prototype") {
54
- throw new Error(`Unknown tool: ${request.params.name}`);
55
- }
56
- const args = ScanArgsSchema.parse(request.params.arguments);
57
- const token = args.token || process.env.FIGMA_TOKEN;
58
- if (!token) {
59
- return {
60
- content: [{
61
- type: "text",
62
- text: 'Error: Figma token required. Pass it as "token" parameter or set FIGMA_TOKEN env var.'
63
- }],
64
- isError: true
65
- };
66
- }
67
- try {
68
- const { fileKey, pageIds } = parseFigmaInput(args.file_key);
69
- const client = new FigmaClient(token);
70
- const file = await client.getFile(fileKey);
71
- const result = await scan(file, {
72
- fileKey,
73
- minTouchTarget: args.min_touch_target,
74
- skip: args.skip,
75
- pageIds: pageIds.length ? pageIds : void 0
76
- });
77
- const output = args.format === "json" ? formatJson(result) : formatTerminal(result);
78
- return {
79
- content: [{ type: "text", text: output }]
80
- };
81
- } catch (error) {
82
- const message = error instanceof FigmaApiError ? error.message : error instanceof Error ? error.message : String(error);
83
- return {
84
- content: [{ type: "text", text: `Error: ${message}` }],
85
- isError: true
86
- };
87
93
  }
88
- });
94
+ );
89
95
  async function main() {
90
96
  const transport = new StdioServerTransport();
91
97
  await server.connect(transport);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@protoscan/mcp",
3
- "version": "0.0.1",
4
- "description": "MCP server for ProtoScan — expose Figma prototype QA as a tool for Claude and other MCP clients.",
3
+ "version": "0.0.2",
4
+ "description": "MCP server for ProtoScan — expose Figma prototype QA as a tool for Claude and other MCP clients. Supports MCP Apps for interactive HTML reports inside Claude.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -14,7 +14,10 @@
14
14
  "bin": {
15
15
  "mcp-protoscan": "./dist/index.js"
16
16
  },
17
- "files": ["dist", "README.md"],
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
18
21
  "scripts": {
19
22
  "build": "tsup src/index.ts --format esm --dts",
20
23
  "prepublishOnly": "npm run build",
@@ -25,6 +28,7 @@
25
28
  "access": "public"
26
29
  },
27
30
  "dependencies": {
31
+ "@modelcontextprotocol/ext-apps": "^1.7.2",
28
32
  "@modelcontextprotocol/sdk": "^1.17.1",
29
33
  "@protoscan/core": "0.0.1",
30
34
  "zod": "^3.23.8"