@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.
- package/dist/index.js +74 -68
- 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 {
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
23
|
+
var server = new McpServer(
|
|
30
24
|
{ name: "protoscan", version: "0.0.1" },
|
|
31
|
-
{ capabilities: {
|
|
25
|
+
{ capabilities: { resources: {} } }
|
|
32
26
|
);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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.
|
|
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": [
|
|
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"
|