@ufira/vibma 0.2.2 → 0.3.1
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 +7 -7
- package/dist/mcp.cjs +807 -1429
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.js +807 -1430
- package/dist/mcp.js.map +1 -1
- package/dist/tools/endpoint.cjs +119 -0
- package/dist/tools/endpoint.cjs.map +1 -0
- package/dist/tools/endpoint.d.cts +94 -0
- package/dist/tools/endpoint.d.ts +94 -0
- package/dist/tools/endpoint.js +92 -0
- package/dist/tools/endpoint.js.map +1 -0
- package/dist/tools/registry.cjs +73 -0
- package/dist/tools/registry.cjs.map +1 -0
- package/dist/tools/registry.d.cts +14 -0
- package/dist/tools/registry.d.ts +14 -0
- package/dist/tools/registry.js +47 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/schemas.cjs +101 -0
- package/dist/tools/schemas.cjs.map +1 -0
- package/dist/tools/schemas.d.cts +52 -0
- package/dist/tools/schemas.d.ts +52 -0
- package/dist/tools/schemas.js +70 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools/types.cjs +52 -0
- package/dist/tools/types.cjs.map +1 -0
- package/dist/tools/types.d.cts +53 -0
- package/dist/tools/types.d.ts +53 -0
- package/dist/tools/types.js +27 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/utils/coercion.cjs +56 -0
- package/dist/utils/coercion.cjs.map +1 -0
- package/dist/utils/coercion.d.cts +10 -0
- package/dist/utils/coercion.d.ts +10 -0
- package/dist/utils/coercion.js +30 -0
- package/dist/utils/coercion.js.map +1 -0
- package/dist/utils/color.cjs +38 -0
- package/dist/utils/color.cjs.map +1 -0
- package/dist/utils/color.d.cts +4 -0
- package/dist/utils/color.d.ts +4 -0
- package/dist/utils/color.js +14 -0
- package/dist/utils/color.js.map +1 -0
- package/dist/utils/wcag.cjs +123 -0
- package/dist/utils/wcag.cjs.map +1 -0
- package/dist/utils/wcag.d.cts +80 -0
- package/dist/utils/wcag.d.ts +80 -0
- package/dist/utils/wcag.js +92 -0
- package/dist/utils/wcag.js.map +1 -0
- package/package.json +44 -15
- package/LICENSE +0 -22
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/tools/endpoint.ts
|
|
20
|
+
var endpoint_exports = {};
|
|
21
|
+
__export(endpoint_exports, {
|
|
22
|
+
createDispatcher: () => createDispatcher,
|
|
23
|
+
endpointSchema: () => endpointSchema,
|
|
24
|
+
paginate: () => paginate,
|
|
25
|
+
pickFields: () => pickFields
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(endpoint_exports);
|
|
28
|
+
var import_zod2 = require("zod");
|
|
29
|
+
|
|
30
|
+
// src/utils/coercion.ts
|
|
31
|
+
var import_zod = require("zod");
|
|
32
|
+
var flexJson = (inner) => import_zod.z.preprocess((v) => {
|
|
33
|
+
if (typeof v === "string") {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(v);
|
|
36
|
+
} catch {
|
|
37
|
+
return v;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return v;
|
|
41
|
+
}, inner);
|
|
42
|
+
|
|
43
|
+
// src/tools/endpoint.ts
|
|
44
|
+
function pickFields(obj, fields) {
|
|
45
|
+
if (fields.includes("*")) return obj;
|
|
46
|
+
const keep = /* @__PURE__ */ new Set([...fields, "id", "name", "type"]);
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const key of Object.keys(obj)) {
|
|
49
|
+
if (keep.has(key)) out[key] = obj[key];
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
function paginate(items, offset = 0, limit = 100) {
|
|
54
|
+
const sliced = items.slice(offset, offset + limit);
|
|
55
|
+
return { totalCount: items.length, returned: sliced.length, offset, limit, items: sliced };
|
|
56
|
+
}
|
|
57
|
+
var DEFAULT_TIERS = {
|
|
58
|
+
get: "read",
|
|
59
|
+
list: "read",
|
|
60
|
+
create: "create",
|
|
61
|
+
update: "edit",
|
|
62
|
+
delete: "edit"
|
|
63
|
+
};
|
|
64
|
+
function endpointSchema(methods, capsOrExtra, extraOrTiers, methodTiers) {
|
|
65
|
+
let caps;
|
|
66
|
+
let extra;
|
|
67
|
+
if (capsOrExtra && "create" in capsOrExtra && "edit" in capsOrExtra && typeof capsOrExtra.create === "boolean") {
|
|
68
|
+
caps = capsOrExtra;
|
|
69
|
+
extra = extraOrTiers;
|
|
70
|
+
} else {
|
|
71
|
+
extra = capsOrExtra;
|
|
72
|
+
}
|
|
73
|
+
let filtered = methods;
|
|
74
|
+
if (caps) {
|
|
75
|
+
const tiers = { ...DEFAULT_TIERS, ...methodTiers };
|
|
76
|
+
filtered = methods.filter((m) => {
|
|
77
|
+
const tier = tiers[m] ?? "edit";
|
|
78
|
+
if (tier === "read") return true;
|
|
79
|
+
if (tier === "create") return caps.create;
|
|
80
|
+
if (tier === "edit") return caps.edit;
|
|
81
|
+
return false;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const schema = {
|
|
85
|
+
method: import_zod2.z.enum(filtered)
|
|
86
|
+
};
|
|
87
|
+
if (filtered.includes("get") || filtered.includes("delete")) {
|
|
88
|
+
schema.id = import_zod2.z.string().optional().describe("Resource ID (get, delete)");
|
|
89
|
+
}
|
|
90
|
+
if (filtered.includes("get") || filtered.includes("list")) {
|
|
91
|
+
schema.fields = flexJson(import_zod2.z.array(import_zod2.z.string())).optional().describe('Property whitelist (get/list). Identity fields (id, name, type) always included. Omit for stubs on list, full detail on get. Pass ["*"] for all fields.');
|
|
92
|
+
}
|
|
93
|
+
if (filtered.includes("list")) {
|
|
94
|
+
schema.offset = import_zod2.z.coerce.number().optional().describe("Skip N items for pagination (default 0)");
|
|
95
|
+
schema.limit = import_zod2.z.coerce.number().optional().describe("Max items per page (default 100)");
|
|
96
|
+
}
|
|
97
|
+
return { ...schema, ...extra };
|
|
98
|
+
}
|
|
99
|
+
function createDispatcher(handlers) {
|
|
100
|
+
const supported = Object.keys(handlers).join(", ");
|
|
101
|
+
return async (params) => {
|
|
102
|
+
const method = params.method;
|
|
103
|
+
const handler = handlers[method];
|
|
104
|
+
if (!handler) throw new Error(`Method '${method}' not supported. Available: ${supported}`);
|
|
105
|
+
let result = await handler(params);
|
|
106
|
+
if (method === "get" && params.fields?.length && result && typeof result === "object") {
|
|
107
|
+
result = pickFields(result, params.fields);
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
113
|
+
0 && (module.exports = {
|
|
114
|
+
createDispatcher,
|
|
115
|
+
endpointSchema,
|
|
116
|
+
paginate,
|
|
117
|
+
pickFields
|
|
118
|
+
});
|
|
119
|
+
//# sourceMappingURL=endpoint.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tools/endpoint.ts","../../src/utils/coercion.ts"],"sourcesContent":["/**\n * Shared endpoint infrastructure.\n *\n * Every resource endpoint follows the same contract:\n * create → items[{...}] → { results: [{id}, ...] }\n * get → { id, fields? } → resource object (field-filtered)\n * list → { filters?, fields?, offset, limit } → { totalCount, returned, offset, limit, items: [...] }\n * update → items[{...}] → { results: [\"ok\", ...] }\n * delete → { id } or items[{id}] → \"ok\" or { results: [\"ok\", ...] }\n *\n * MCP side: endpointSchema()\n * Figma side: createDispatcher() + paginate()\n */\n\nimport { z } from \"zod\";\nimport { flexJson } from \"../utils/coercion\";\nimport type { Capabilities } from \"./types\";\n\n// ─── Method Types ────────────────────────────────────────────────\n\nexport type EndpointMethod = \"create\" | \"get\" | \"list\" | \"update\" | \"delete\";\n\n// ─── Response Types ──────────────────────────────────────────────\n\n/** Batch response envelope (create, update, delete). Produced by batchHandler. */\nexport interface BatchResponse<T = { id: string }> {\n results: Array<T | \"ok\" | { error: string }>;\n warnings?: string[];\n deferred?: string;\n}\n\n/** Paginated list response envelope. */\nexport interface ListResponse<T = Record<string, any>> {\n totalCount: number;\n returned: number;\n offset: number;\n limit: number;\n items: T[];\n}\n\n// ─── Discriminated Param Types ───────────────────────────────────\n//\n// Each endpoint specializes these with its own create/update item\n// types and list filters. Example:\n//\n// type StyleParams = EndpointParams<StyleCreateItem, StyleUpdateItem, { type?: string }>;\n//\n\nexport type EndpointParams<\n TCreate = Record<string, any>,\n TUpdate = Record<string, any>,\n TListFilters = Record<string, never>,\n> =\n | { method: \"create\"; items: TCreate[] }\n | { method: \"get\"; id: string; fields?: string[] }\n | ({ method: \"list\"; fields?: string[]; offset?: number; limit?: number } & TListFilters)\n | { method: \"update\"; items: TUpdate[] }\n | { method: \"delete\"; id?: string; items?: Array<{ id: string }> };\n\n// ─── Helpers ────────────────────────────────────────────────────\n\n/**\n * Top-level field filter for get/list responses.\n * Always preserves identity fields (id, name, type).\n * Pass [\"*\"] to return all fields.\n */\nexport function pickFields(obj: Record<string, any>, fields: string[]): Record<string, any> {\n if (fields.includes(\"*\")) return obj;\n const keep = new Set([...fields, \"id\", \"name\", \"type\"]);\n const out: Record<string, any> = {};\n for (const key of Object.keys(obj)) {\n if (keep.has(key)) out[key] = obj[key];\n }\n return out;\n}\n\n/**\n * Paginate an array of items. Default limit: 100.\n * Call from list handlers after assembling the full result set.\n */\nexport function paginate<T>(items: T[], offset = 0, limit = 100): ListResponse<T> {\n const sliced = items.slice(offset, offset + limit);\n return { totalCount: items.length, returned: sliced.length, offset, limit, items: sliced };\n}\n\n// ─── Schema Builder ──────────────────────────────────────────────\n\n/** Maps each endpoint method to the minimum tier required to use it. */\nexport type MethodTier = \"read\" | \"create\" | \"edit\";\n\nconst DEFAULT_TIERS: Record<string, MethodTier> = {\n get: \"read\", list: \"read\", create: \"create\", update: \"edit\", delete: \"edit\",\n};\n\n/**\n * Build standard endpoint Zod schema fields.\n *\n * Always includes `method`. Auto-adds:\n * - `id` when get/delete are in the method list\n * - `fields` when get or list are in the method list\n * - `offset`/`limit` when list is in the method list\n * Merge endpoint-specific fields (items, list filters) via `extra`.\n *\n * When `caps` is provided, methods are filtered by tier — only methods\n * whose tier is enabled appear in the enum.\n */\nexport function endpointSchema(\n methods: string[],\n capsOrExtra?: Capabilities | Record<string, z.ZodTypeAny>,\n extraOrTiers?: Record<string, z.ZodTypeAny>,\n methodTiers?: Record<string, MethodTier>,\n): Record<string, z.ZodTypeAny> {\n // Overload resolution: (methods, extra?) or (methods, caps, extra?, tiers?)\n let caps: Capabilities | undefined;\n let extra: Record<string, z.ZodTypeAny> | undefined;\n\n if (capsOrExtra && (\"create\" in capsOrExtra) && (\"edit\" in capsOrExtra)\n && typeof (capsOrExtra as any).create === \"boolean\") {\n caps = capsOrExtra as Capabilities;\n extra = extraOrTiers;\n } else {\n extra = capsOrExtra as Record<string, z.ZodTypeAny> | undefined;\n // methodTiers and extraOrTiers are unused in the legacy call signature\n }\n\n // Filter methods by capabilities\n let filtered = methods;\n if (caps) {\n const tiers = { ...DEFAULT_TIERS, ...methodTiers };\n filtered = methods.filter(m => {\n const tier = tiers[m] ?? \"edit\"; // unknown methods default to edit\n if (tier === \"read\") return true;\n if (tier === \"create\") return caps!.create;\n if (tier === \"edit\") return caps!.edit;\n return false;\n });\n }\n\n const schema: Record<string, z.ZodTypeAny> = {\n method: z.enum(filtered as [string, ...string[]]),\n };\n if (filtered.includes(\"get\") || filtered.includes(\"delete\")) {\n schema.id = z.string().optional().describe(\"Resource ID (get, delete)\");\n }\n if (filtered.includes(\"get\") || filtered.includes(\"list\")) {\n schema.fields = flexJson(z.array(z.string())).optional()\n .describe('Property whitelist (get/list). Identity fields (id, name, type) always included. Omit for stubs on list, full detail on get. Pass [\"*\"] for all fields.');\n }\n if (filtered.includes(\"list\")) {\n schema.offset = z.coerce.number().optional().describe(\"Skip N items for pagination (default 0)\");\n schema.limit = z.coerce.number().optional().describe(\"Max items per page (default 100)\");\n }\n return { ...schema, ...extra };\n}\n\n// ─── Figma Dispatcher ────────────────────────────────────────────\n\ntype MethodHandlers = Record<string, (params: any) => Promise<any>>;\n\n/**\n * Create a Figma handler that dispatches on `params.method`.\n * Only methods with registered handlers are allowed.\n * Automatically applies `fields` filtering on get responses.\n */\nexport function createDispatcher(handlers: MethodHandlers) {\n const supported = Object.keys(handlers).join(\", \");\n return async (params: any): Promise<any> => {\n const method = params.method as EndpointMethod;\n const handler = handlers[method];\n if (!handler) throw new Error(`Method '${method}' not supported. Available: ${supported}`);\n let result = await handler(params);\n // Auto-apply fields filtering on get responses (full detail by default)\n if (method === \"get\" && params.fields?.length && result && typeof result === \"object\") {\n result = pickFields(result, params.fields);\n }\n return result;\n };\n}\n","import { z } from \"zod\";\n\n// AI agents (Claude, GPT, etc.) frequently pass numbers as strings\n// (\"10\" instead of 10), booleans as strings (\"true\" instead of true),\n// and objects/arrays as JSON strings. These helpers add resilient\n// coercion so tools don't fail on valid-but-mistyped input.\n\n/** Coerce \"true\"/\"false\"/\"1\"/\"0\" strings to boolean */\nexport const flexBool = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (v === \"true\" || v === \"1\") return true;\n if (v === \"false\" || v === \"0\") return false;\n return v;\n }, inner);\n\n/** Coerce JSON strings to parsed values (for objects/arrays that agents may stringify) */\nexport const flexJson = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n try {\n return JSON.parse(v);\n } catch {\n return v;\n }\n }\n return v;\n }, inner);\n\n/** Coerce numeric strings only when they're valid numbers (safe for use inside unions) */\nexport const flexNum = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n const n = Number(v);\n if (!isNaN(n) && v.trim() !== \"\") return n;\n }\n return v;\n }, inner);\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,IAAAA,cAAkB;;;ACdlB,iBAAkB;AAgBX,IAAM,WAAW,CAAyB,UAC/C,aAAE,WAAW,CAAC,MAAM;AAClB,MAAI,OAAO,MAAM,UAAU;AACzB,QAAI;AACF,aAAO,KAAK,MAAM,CAAC;AAAA,IACrB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT,GAAG,KAAK;;;ADwCH,SAAS,WAAW,KAA0B,QAAuC;AAC1F,MAAI,OAAO,SAAS,GAAG,EAAG,QAAO;AACjC,QAAM,OAAO,oBAAI,IAAI,CAAC,GAAG,QAAQ,MAAM,QAAQ,MAAM,CAAC;AACtD,QAAM,MAA2B,CAAC;AAClC,aAAW,OAAO,OAAO,KAAK,GAAG,GAAG;AAClC,QAAI,KAAK,IAAI,GAAG,EAAG,KAAI,GAAG,IAAI,IAAI,GAAG;AAAA,EACvC;AACA,SAAO;AACT;AAMO,SAAS,SAAY,OAAY,SAAS,GAAG,QAAQ,KAAsB;AAChF,QAAM,SAAS,MAAM,MAAM,QAAQ,SAAS,KAAK;AACjD,SAAO,EAAE,YAAY,MAAM,QAAQ,UAAU,OAAO,QAAQ,QAAQ,OAAO,OAAO,OAAO;AAC3F;AAOA,IAAM,gBAA4C;AAAA,EAChD,KAAK;AAAA,EAAQ,MAAM;AAAA,EAAQ,QAAQ;AAAA,EAAU,QAAQ;AAAA,EAAQ,QAAQ;AACvE;AAcO,SAAS,eACd,SACA,aACA,cACA,aAC8B;AAE9B,MAAI;AACJ,MAAI;AAEJ,MAAI,eAAgB,YAAY,eAAiB,UAAU,eACpD,OAAQ,YAAoB,WAAW,WAAW;AACvD,WAAO;AACP,YAAQ;AAAA,EACV,OAAO;AACL,YAAQ;AAAA,EAEV;AAGA,MAAI,WAAW;AACf,MAAI,MAAM;AACR,UAAM,QAAQ,EAAE,GAAG,eAAe,GAAG,YAAY;AACjD,eAAW,QAAQ,OAAO,OAAK;AAC7B,YAAM,OAAO,MAAM,CAAC,KAAK;AACzB,UAAI,SAAS,OAAQ,QAAO;AAC5B,UAAI,SAAS,SAAU,QAAO,KAAM;AACpC,UAAI,SAAS,OAAQ,QAAO,KAAM;AAClC,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,QAAM,SAAuC;AAAA,IAC3C,QAAQ,cAAE,KAAK,QAAiC;AAAA,EAClD;AACA,MAAI,SAAS,SAAS,KAAK,KAAK,SAAS,SAAS,QAAQ,GAAG;AAC3D,WAAO,KAAK,cAAE,OAAO,EAAE,SAAS,EAAE,SAAS,2BAA2B;AAAA,EACxE;AACA,MAAI,SAAS,SAAS,KAAK,KAAK,SAAS,SAAS,MAAM,GAAG;AACzD,WAAO,SAAS,SAAS,cAAE,MAAM,cAAE,OAAO,CAAC,CAAC,EAAE,SAAS,EACpD,SAAS,yJAAyJ;AAAA,EACvK;AACA,MAAI,SAAS,SAAS,MAAM,GAAG;AAC7B,WAAO,SAAS,cAAE,OAAO,OAAO,EAAE,SAAS,EAAE,SAAS,yCAAyC;AAC/F,WAAO,QAAQ,cAAE,OAAO,OAAO,EAAE,SAAS,EAAE,SAAS,kCAAkC;AAAA,EACzF;AACA,SAAO,EAAE,GAAG,QAAQ,GAAG,MAAM;AAC/B;AAWO,SAAS,iBAAiB,UAA0B;AACzD,QAAM,YAAY,OAAO,KAAK,QAAQ,EAAE,KAAK,IAAI;AACjD,SAAO,OAAO,WAA8B;AAC1C,UAAM,SAAS,OAAO;AACtB,UAAM,UAAU,SAAS,MAAM;AAC/B,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,WAAW,MAAM,+BAA+B,SAAS,EAAE;AACzF,QAAI,SAAS,MAAM,QAAQ,MAAM;AAEjC,QAAI,WAAW,SAAS,OAAO,QAAQ,UAAU,UAAU,OAAO,WAAW,UAAU;AACrF,eAAS,WAAW,QAAQ,OAAO,MAAM;AAAA,IAC3C;AACA,WAAO;AAAA,EACT;AACF;","names":["import_zod"]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Capabilities } from './types.cjs';
|
|
3
|
+
import '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared endpoint infrastructure.
|
|
7
|
+
*
|
|
8
|
+
* Every resource endpoint follows the same contract:
|
|
9
|
+
* create → items[{...}] → { results: [{id}, ...] }
|
|
10
|
+
* get → { id, fields? } → resource object (field-filtered)
|
|
11
|
+
* list → { filters?, fields?, offset, limit } → { totalCount, returned, offset, limit, items: [...] }
|
|
12
|
+
* update → items[{...}] → { results: ["ok", ...] }
|
|
13
|
+
* delete → { id } or items[{id}] → "ok" or { results: ["ok", ...] }
|
|
14
|
+
*
|
|
15
|
+
* MCP side: endpointSchema()
|
|
16
|
+
* Figma side: createDispatcher() + paginate()
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
type EndpointMethod = "create" | "get" | "list" | "update" | "delete";
|
|
20
|
+
/** Batch response envelope (create, update, delete). Produced by batchHandler. */
|
|
21
|
+
interface BatchResponse<T = {
|
|
22
|
+
id: string;
|
|
23
|
+
}> {
|
|
24
|
+
results: Array<T | "ok" | {
|
|
25
|
+
error: string;
|
|
26
|
+
}>;
|
|
27
|
+
warnings?: string[];
|
|
28
|
+
deferred?: string;
|
|
29
|
+
}
|
|
30
|
+
/** Paginated list response envelope. */
|
|
31
|
+
interface ListResponse<T = Record<string, any>> {
|
|
32
|
+
totalCount: number;
|
|
33
|
+
returned: number;
|
|
34
|
+
offset: number;
|
|
35
|
+
limit: number;
|
|
36
|
+
items: T[];
|
|
37
|
+
}
|
|
38
|
+
type EndpointParams<TCreate = Record<string, any>, TUpdate = Record<string, any>, TListFilters = Record<string, never>> = {
|
|
39
|
+
method: "create";
|
|
40
|
+
items: TCreate[];
|
|
41
|
+
} | {
|
|
42
|
+
method: "get";
|
|
43
|
+
id: string;
|
|
44
|
+
fields?: string[];
|
|
45
|
+
} | ({
|
|
46
|
+
method: "list";
|
|
47
|
+
fields?: string[];
|
|
48
|
+
offset?: number;
|
|
49
|
+
limit?: number;
|
|
50
|
+
} & TListFilters) | {
|
|
51
|
+
method: "update";
|
|
52
|
+
items: TUpdate[];
|
|
53
|
+
} | {
|
|
54
|
+
method: "delete";
|
|
55
|
+
id?: string;
|
|
56
|
+
items?: Array<{
|
|
57
|
+
id: string;
|
|
58
|
+
}>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Top-level field filter for get/list responses.
|
|
62
|
+
* Always preserves identity fields (id, name, type).
|
|
63
|
+
* Pass ["*"] to return all fields.
|
|
64
|
+
*/
|
|
65
|
+
declare function pickFields(obj: Record<string, any>, fields: string[]): Record<string, any>;
|
|
66
|
+
/**
|
|
67
|
+
* Paginate an array of items. Default limit: 100.
|
|
68
|
+
* Call from list handlers after assembling the full result set.
|
|
69
|
+
*/
|
|
70
|
+
declare function paginate<T>(items: T[], offset?: number, limit?: number): ListResponse<T>;
|
|
71
|
+
/** Maps each endpoint method to the minimum tier required to use it. */
|
|
72
|
+
type MethodTier = "read" | "create" | "edit";
|
|
73
|
+
/**
|
|
74
|
+
* Build standard endpoint Zod schema fields.
|
|
75
|
+
*
|
|
76
|
+
* Always includes `method`. Auto-adds:
|
|
77
|
+
* - `id` when get/delete are in the method list
|
|
78
|
+
* - `fields` when get or list are in the method list
|
|
79
|
+
* - `offset`/`limit` when list is in the method list
|
|
80
|
+
* Merge endpoint-specific fields (items, list filters) via `extra`.
|
|
81
|
+
*
|
|
82
|
+
* When `caps` is provided, methods are filtered by tier — only methods
|
|
83
|
+
* whose tier is enabled appear in the enum.
|
|
84
|
+
*/
|
|
85
|
+
declare function endpointSchema(methods: string[], capsOrExtra?: Capabilities | Record<string, z.ZodTypeAny>, extraOrTiers?: Record<string, z.ZodTypeAny>, methodTiers?: Record<string, MethodTier>): Record<string, z.ZodTypeAny>;
|
|
86
|
+
type MethodHandlers = Record<string, (params: any) => Promise<any>>;
|
|
87
|
+
/**
|
|
88
|
+
* Create a Figma handler that dispatches on `params.method`.
|
|
89
|
+
* Only methods with registered handlers are allowed.
|
|
90
|
+
* Automatically applies `fields` filtering on get responses.
|
|
91
|
+
*/
|
|
92
|
+
declare function createDispatcher(handlers: MethodHandlers): (params: any) => Promise<any>;
|
|
93
|
+
|
|
94
|
+
export { type BatchResponse, type EndpointMethod, type EndpointParams, type ListResponse, type MethodTier, createDispatcher, endpointSchema, paginate, pickFields };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Capabilities } from './types.js';
|
|
3
|
+
import '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared endpoint infrastructure.
|
|
7
|
+
*
|
|
8
|
+
* Every resource endpoint follows the same contract:
|
|
9
|
+
* create → items[{...}] → { results: [{id}, ...] }
|
|
10
|
+
* get → { id, fields? } → resource object (field-filtered)
|
|
11
|
+
* list → { filters?, fields?, offset, limit } → { totalCount, returned, offset, limit, items: [...] }
|
|
12
|
+
* update → items[{...}] → { results: ["ok", ...] }
|
|
13
|
+
* delete → { id } or items[{id}] → "ok" or { results: ["ok", ...] }
|
|
14
|
+
*
|
|
15
|
+
* MCP side: endpointSchema()
|
|
16
|
+
* Figma side: createDispatcher() + paginate()
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
type EndpointMethod = "create" | "get" | "list" | "update" | "delete";
|
|
20
|
+
/** Batch response envelope (create, update, delete). Produced by batchHandler. */
|
|
21
|
+
interface BatchResponse<T = {
|
|
22
|
+
id: string;
|
|
23
|
+
}> {
|
|
24
|
+
results: Array<T | "ok" | {
|
|
25
|
+
error: string;
|
|
26
|
+
}>;
|
|
27
|
+
warnings?: string[];
|
|
28
|
+
deferred?: string;
|
|
29
|
+
}
|
|
30
|
+
/** Paginated list response envelope. */
|
|
31
|
+
interface ListResponse<T = Record<string, any>> {
|
|
32
|
+
totalCount: number;
|
|
33
|
+
returned: number;
|
|
34
|
+
offset: number;
|
|
35
|
+
limit: number;
|
|
36
|
+
items: T[];
|
|
37
|
+
}
|
|
38
|
+
type EndpointParams<TCreate = Record<string, any>, TUpdate = Record<string, any>, TListFilters = Record<string, never>> = {
|
|
39
|
+
method: "create";
|
|
40
|
+
items: TCreate[];
|
|
41
|
+
} | {
|
|
42
|
+
method: "get";
|
|
43
|
+
id: string;
|
|
44
|
+
fields?: string[];
|
|
45
|
+
} | ({
|
|
46
|
+
method: "list";
|
|
47
|
+
fields?: string[];
|
|
48
|
+
offset?: number;
|
|
49
|
+
limit?: number;
|
|
50
|
+
} & TListFilters) | {
|
|
51
|
+
method: "update";
|
|
52
|
+
items: TUpdate[];
|
|
53
|
+
} | {
|
|
54
|
+
method: "delete";
|
|
55
|
+
id?: string;
|
|
56
|
+
items?: Array<{
|
|
57
|
+
id: string;
|
|
58
|
+
}>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Top-level field filter for get/list responses.
|
|
62
|
+
* Always preserves identity fields (id, name, type).
|
|
63
|
+
* Pass ["*"] to return all fields.
|
|
64
|
+
*/
|
|
65
|
+
declare function pickFields(obj: Record<string, any>, fields: string[]): Record<string, any>;
|
|
66
|
+
/**
|
|
67
|
+
* Paginate an array of items. Default limit: 100.
|
|
68
|
+
* Call from list handlers after assembling the full result set.
|
|
69
|
+
*/
|
|
70
|
+
declare function paginate<T>(items: T[], offset?: number, limit?: number): ListResponse<T>;
|
|
71
|
+
/** Maps each endpoint method to the minimum tier required to use it. */
|
|
72
|
+
type MethodTier = "read" | "create" | "edit";
|
|
73
|
+
/**
|
|
74
|
+
* Build standard endpoint Zod schema fields.
|
|
75
|
+
*
|
|
76
|
+
* Always includes `method`. Auto-adds:
|
|
77
|
+
* - `id` when get/delete are in the method list
|
|
78
|
+
* - `fields` when get or list are in the method list
|
|
79
|
+
* - `offset`/`limit` when list is in the method list
|
|
80
|
+
* Merge endpoint-specific fields (items, list filters) via `extra`.
|
|
81
|
+
*
|
|
82
|
+
* When `caps` is provided, methods are filtered by tier — only methods
|
|
83
|
+
* whose tier is enabled appear in the enum.
|
|
84
|
+
*/
|
|
85
|
+
declare function endpointSchema(methods: string[], capsOrExtra?: Capabilities | Record<string, z.ZodTypeAny>, extraOrTiers?: Record<string, z.ZodTypeAny>, methodTiers?: Record<string, MethodTier>): Record<string, z.ZodTypeAny>;
|
|
86
|
+
type MethodHandlers = Record<string, (params: any) => Promise<any>>;
|
|
87
|
+
/**
|
|
88
|
+
* Create a Figma handler that dispatches on `params.method`.
|
|
89
|
+
* Only methods with registered handlers are allowed.
|
|
90
|
+
* Automatically applies `fields` filtering on get responses.
|
|
91
|
+
*/
|
|
92
|
+
declare function createDispatcher(handlers: MethodHandlers): (params: any) => Promise<any>;
|
|
93
|
+
|
|
94
|
+
export { type BatchResponse, type EndpointMethod, type EndpointParams, type ListResponse, type MethodTier, createDispatcher, endpointSchema, paginate, pickFields };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/tools/endpoint.ts
|
|
2
|
+
import { z as z2 } from "zod";
|
|
3
|
+
|
|
4
|
+
// src/utils/coercion.ts
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
var flexJson = (inner) => z.preprocess((v) => {
|
|
7
|
+
if (typeof v === "string") {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(v);
|
|
10
|
+
} catch {
|
|
11
|
+
return v;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return v;
|
|
15
|
+
}, inner);
|
|
16
|
+
|
|
17
|
+
// src/tools/endpoint.ts
|
|
18
|
+
function pickFields(obj, fields) {
|
|
19
|
+
if (fields.includes("*")) return obj;
|
|
20
|
+
const keep = /* @__PURE__ */ new Set([...fields, "id", "name", "type"]);
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const key of Object.keys(obj)) {
|
|
23
|
+
if (keep.has(key)) out[key] = obj[key];
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
function paginate(items, offset = 0, limit = 100) {
|
|
28
|
+
const sliced = items.slice(offset, offset + limit);
|
|
29
|
+
return { totalCount: items.length, returned: sliced.length, offset, limit, items: sliced };
|
|
30
|
+
}
|
|
31
|
+
var DEFAULT_TIERS = {
|
|
32
|
+
get: "read",
|
|
33
|
+
list: "read",
|
|
34
|
+
create: "create",
|
|
35
|
+
update: "edit",
|
|
36
|
+
delete: "edit"
|
|
37
|
+
};
|
|
38
|
+
function endpointSchema(methods, capsOrExtra, extraOrTiers, methodTiers) {
|
|
39
|
+
let caps;
|
|
40
|
+
let extra;
|
|
41
|
+
if (capsOrExtra && "create" in capsOrExtra && "edit" in capsOrExtra && typeof capsOrExtra.create === "boolean") {
|
|
42
|
+
caps = capsOrExtra;
|
|
43
|
+
extra = extraOrTiers;
|
|
44
|
+
} else {
|
|
45
|
+
extra = capsOrExtra;
|
|
46
|
+
}
|
|
47
|
+
let filtered = methods;
|
|
48
|
+
if (caps) {
|
|
49
|
+
const tiers = { ...DEFAULT_TIERS, ...methodTiers };
|
|
50
|
+
filtered = methods.filter((m) => {
|
|
51
|
+
const tier = tiers[m] ?? "edit";
|
|
52
|
+
if (tier === "read") return true;
|
|
53
|
+
if (tier === "create") return caps.create;
|
|
54
|
+
if (tier === "edit") return caps.edit;
|
|
55
|
+
return false;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const schema = {
|
|
59
|
+
method: z2.enum(filtered)
|
|
60
|
+
};
|
|
61
|
+
if (filtered.includes("get") || filtered.includes("delete")) {
|
|
62
|
+
schema.id = z2.string().optional().describe("Resource ID (get, delete)");
|
|
63
|
+
}
|
|
64
|
+
if (filtered.includes("get") || filtered.includes("list")) {
|
|
65
|
+
schema.fields = flexJson(z2.array(z2.string())).optional().describe('Property whitelist (get/list). Identity fields (id, name, type) always included. Omit for stubs on list, full detail on get. Pass ["*"] for all fields.');
|
|
66
|
+
}
|
|
67
|
+
if (filtered.includes("list")) {
|
|
68
|
+
schema.offset = z2.coerce.number().optional().describe("Skip N items for pagination (default 0)");
|
|
69
|
+
schema.limit = z2.coerce.number().optional().describe("Max items per page (default 100)");
|
|
70
|
+
}
|
|
71
|
+
return { ...schema, ...extra };
|
|
72
|
+
}
|
|
73
|
+
function createDispatcher(handlers) {
|
|
74
|
+
const supported = Object.keys(handlers).join(", ");
|
|
75
|
+
return async (params) => {
|
|
76
|
+
const method = params.method;
|
|
77
|
+
const handler = handlers[method];
|
|
78
|
+
if (!handler) throw new Error(`Method '${method}' not supported. Available: ${supported}`);
|
|
79
|
+
let result = await handler(params);
|
|
80
|
+
if (method === "get" && params.fields?.length && result && typeof result === "object") {
|
|
81
|
+
result = pickFields(result, params.fields);
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export {
|
|
87
|
+
createDispatcher,
|
|
88
|
+
endpointSchema,
|
|
89
|
+
paginate,
|
|
90
|
+
pickFields
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=endpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tools/endpoint.ts","../../src/utils/coercion.ts"],"sourcesContent":["/**\n * Shared endpoint infrastructure.\n *\n * Every resource endpoint follows the same contract:\n * create → items[{...}] → { results: [{id}, ...] }\n * get → { id, fields? } → resource object (field-filtered)\n * list → { filters?, fields?, offset, limit } → { totalCount, returned, offset, limit, items: [...] }\n * update → items[{...}] → { results: [\"ok\", ...] }\n * delete → { id } or items[{id}] → \"ok\" or { results: [\"ok\", ...] }\n *\n * MCP side: endpointSchema()\n * Figma side: createDispatcher() + paginate()\n */\n\nimport { z } from \"zod\";\nimport { flexJson } from \"../utils/coercion\";\nimport type { Capabilities } from \"./types\";\n\n// ─── Method Types ────────────────────────────────────────────────\n\nexport type EndpointMethod = \"create\" | \"get\" | \"list\" | \"update\" | \"delete\";\n\n// ─── Response Types ──────────────────────────────────────────────\n\n/** Batch response envelope (create, update, delete). Produced by batchHandler. */\nexport interface BatchResponse<T = { id: string }> {\n results: Array<T | \"ok\" | { error: string }>;\n warnings?: string[];\n deferred?: string;\n}\n\n/** Paginated list response envelope. */\nexport interface ListResponse<T = Record<string, any>> {\n totalCount: number;\n returned: number;\n offset: number;\n limit: number;\n items: T[];\n}\n\n// ─── Discriminated Param Types ───────────────────────────────────\n//\n// Each endpoint specializes these with its own create/update item\n// types and list filters. Example:\n//\n// type StyleParams = EndpointParams<StyleCreateItem, StyleUpdateItem, { type?: string }>;\n//\n\nexport type EndpointParams<\n TCreate = Record<string, any>,\n TUpdate = Record<string, any>,\n TListFilters = Record<string, never>,\n> =\n | { method: \"create\"; items: TCreate[] }\n | { method: \"get\"; id: string; fields?: string[] }\n | ({ method: \"list\"; fields?: string[]; offset?: number; limit?: number } & TListFilters)\n | { method: \"update\"; items: TUpdate[] }\n | { method: \"delete\"; id?: string; items?: Array<{ id: string }> };\n\n// ─── Helpers ────────────────────────────────────────────────────\n\n/**\n * Top-level field filter for get/list responses.\n * Always preserves identity fields (id, name, type).\n * Pass [\"*\"] to return all fields.\n */\nexport function pickFields(obj: Record<string, any>, fields: string[]): Record<string, any> {\n if (fields.includes(\"*\")) return obj;\n const keep = new Set([...fields, \"id\", \"name\", \"type\"]);\n const out: Record<string, any> = {};\n for (const key of Object.keys(obj)) {\n if (keep.has(key)) out[key] = obj[key];\n }\n return out;\n}\n\n/**\n * Paginate an array of items. Default limit: 100.\n * Call from list handlers after assembling the full result set.\n */\nexport function paginate<T>(items: T[], offset = 0, limit = 100): ListResponse<T> {\n const sliced = items.slice(offset, offset + limit);\n return { totalCount: items.length, returned: sliced.length, offset, limit, items: sliced };\n}\n\n// ─── Schema Builder ──────────────────────────────────────────────\n\n/** Maps each endpoint method to the minimum tier required to use it. */\nexport type MethodTier = \"read\" | \"create\" | \"edit\";\n\nconst DEFAULT_TIERS: Record<string, MethodTier> = {\n get: \"read\", list: \"read\", create: \"create\", update: \"edit\", delete: \"edit\",\n};\n\n/**\n * Build standard endpoint Zod schema fields.\n *\n * Always includes `method`. Auto-adds:\n * - `id` when get/delete are in the method list\n * - `fields` when get or list are in the method list\n * - `offset`/`limit` when list is in the method list\n * Merge endpoint-specific fields (items, list filters) via `extra`.\n *\n * When `caps` is provided, methods are filtered by tier — only methods\n * whose tier is enabled appear in the enum.\n */\nexport function endpointSchema(\n methods: string[],\n capsOrExtra?: Capabilities | Record<string, z.ZodTypeAny>,\n extraOrTiers?: Record<string, z.ZodTypeAny>,\n methodTiers?: Record<string, MethodTier>,\n): Record<string, z.ZodTypeAny> {\n // Overload resolution: (methods, extra?) or (methods, caps, extra?, tiers?)\n let caps: Capabilities | undefined;\n let extra: Record<string, z.ZodTypeAny> | undefined;\n\n if (capsOrExtra && (\"create\" in capsOrExtra) && (\"edit\" in capsOrExtra)\n && typeof (capsOrExtra as any).create === \"boolean\") {\n caps = capsOrExtra as Capabilities;\n extra = extraOrTiers;\n } else {\n extra = capsOrExtra as Record<string, z.ZodTypeAny> | undefined;\n // methodTiers and extraOrTiers are unused in the legacy call signature\n }\n\n // Filter methods by capabilities\n let filtered = methods;\n if (caps) {\n const tiers = { ...DEFAULT_TIERS, ...methodTiers };\n filtered = methods.filter(m => {\n const tier = tiers[m] ?? \"edit\"; // unknown methods default to edit\n if (tier === \"read\") return true;\n if (tier === \"create\") return caps!.create;\n if (tier === \"edit\") return caps!.edit;\n return false;\n });\n }\n\n const schema: Record<string, z.ZodTypeAny> = {\n method: z.enum(filtered as [string, ...string[]]),\n };\n if (filtered.includes(\"get\") || filtered.includes(\"delete\")) {\n schema.id = z.string().optional().describe(\"Resource ID (get, delete)\");\n }\n if (filtered.includes(\"get\") || filtered.includes(\"list\")) {\n schema.fields = flexJson(z.array(z.string())).optional()\n .describe('Property whitelist (get/list). Identity fields (id, name, type) always included. Omit for stubs on list, full detail on get. Pass [\"*\"] for all fields.');\n }\n if (filtered.includes(\"list\")) {\n schema.offset = z.coerce.number().optional().describe(\"Skip N items for pagination (default 0)\");\n schema.limit = z.coerce.number().optional().describe(\"Max items per page (default 100)\");\n }\n return { ...schema, ...extra };\n}\n\n// ─── Figma Dispatcher ────────────────────────────────────────────\n\ntype MethodHandlers = Record<string, (params: any) => Promise<any>>;\n\n/**\n * Create a Figma handler that dispatches on `params.method`.\n * Only methods with registered handlers are allowed.\n * Automatically applies `fields` filtering on get responses.\n */\nexport function createDispatcher(handlers: MethodHandlers) {\n const supported = Object.keys(handlers).join(\", \");\n return async (params: any): Promise<any> => {\n const method = params.method as EndpointMethod;\n const handler = handlers[method];\n if (!handler) throw new Error(`Method '${method}' not supported. Available: ${supported}`);\n let result = await handler(params);\n // Auto-apply fields filtering on get responses (full detail by default)\n if (method === \"get\" && params.fields?.length && result && typeof result === \"object\") {\n result = pickFields(result, params.fields);\n }\n return result;\n };\n}\n","import { z } from \"zod\";\n\n// AI agents (Claude, GPT, etc.) frequently pass numbers as strings\n// (\"10\" instead of 10), booleans as strings (\"true\" instead of true),\n// and objects/arrays as JSON strings. These helpers add resilient\n// coercion so tools don't fail on valid-but-mistyped input.\n\n/** Coerce \"true\"/\"false\"/\"1\"/\"0\" strings to boolean */\nexport const flexBool = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (v === \"true\" || v === \"1\") return true;\n if (v === \"false\" || v === \"0\") return false;\n return v;\n }, inner);\n\n/** Coerce JSON strings to parsed values (for objects/arrays that agents may stringify) */\nexport const flexJson = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n try {\n return JSON.parse(v);\n } catch {\n return v;\n }\n }\n return v;\n }, inner);\n\n/** Coerce numeric strings only when they're valid numbers (safe for use inside unions) */\nexport const flexNum = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n const n = Number(v);\n if (!isNaN(n) && v.trim() !== \"\") return n;\n }\n return v;\n }, inner);\n"],"mappings":"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","names":["z","z"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/tools/registry.ts
|
|
20
|
+
var registry_exports = {};
|
|
21
|
+
__export(registry_exports, {
|
|
22
|
+
registerTools: () => registerTools
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(registry_exports);
|
|
25
|
+
|
|
26
|
+
// src/tools/types.ts
|
|
27
|
+
var MAX_RESPONSE_CHARS = 5e4;
|
|
28
|
+
function mcpJson(data) {
|
|
29
|
+
const text = JSON.stringify(data);
|
|
30
|
+
if (text.length <= MAX_RESPONSE_CHARS) {
|
|
31
|
+
return { content: [{ type: "text", text }] };
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
content: [{
|
|
35
|
+
type: "text",
|
|
36
|
+
text: JSON.stringify({
|
|
37
|
+
_error: "response_too_large",
|
|
38
|
+
_sizeKB: Math.round(text.length / 1024),
|
|
39
|
+
warning: "Response exceeds safe size. Use 'depth', 'fields', 'limit', or 'summaryOnly' parameters to reduce response size."
|
|
40
|
+
})
|
|
41
|
+
}]
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function mcpError(prefix, error) {
|
|
45
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
46
|
+
return { content: [{ type: "text", text: `${prefix}: ${msg}` }] };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/tools/registry.ts
|
|
50
|
+
function registerTools(server, sendCommand, caps, tools) {
|
|
51
|
+
for (const tool of tools) {
|
|
52
|
+
if (tool.tier === "create" && !caps.create) continue;
|
|
53
|
+
if (tool.tier === "edit" && !caps.edit) continue;
|
|
54
|
+
const schema = typeof tool.schema === "function" ? tool.schema(caps) : tool.schema;
|
|
55
|
+
const command = tool.command ?? tool.name;
|
|
56
|
+
const timeout = tool.timeout;
|
|
57
|
+
const format = tool.formatResponse ?? mcpJson;
|
|
58
|
+
server.registerTool(tool.name, { description: tool.description, inputSchema: schema }, async (params) => {
|
|
59
|
+
try {
|
|
60
|
+
if (tool.validate) tool.validate(params);
|
|
61
|
+
const result = await sendCommand(command, params, timeout);
|
|
62
|
+
return format(result);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
return mcpError(`${tool.name} error`, e);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
70
|
+
0 && (module.exports = {
|
|
71
|
+
registerTools
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=registry.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tools/registry.ts","../../src/tools/types.ts"],"sourcesContent":["import type { McpServer, SendCommandFn, Capabilities, ToolDef } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\n\n/**\n * Batch-register declarative ToolDefs on the MCP server.\n *\n * 1. Filters by tier: read → always, create → caps.create, edit → caps.edit\n * 2. Resolves dynamic schemas (endpoint tools pass caps-dependent functions)\n * 3. Generates a uniform handler: validate? → sendCommand → formatResponse ?? mcpJson\n */\nexport function registerTools(\n server: McpServer,\n sendCommand: SendCommandFn,\n caps: Capabilities,\n tools: ToolDef[],\n): void {\n for (const tool of tools) {\n // Tier gate\n if (tool.tier === \"create\" && !caps.create) continue;\n if (tool.tier === \"edit\" && !caps.edit) continue;\n\n const schema = typeof tool.schema === \"function\" ? tool.schema(caps) : tool.schema;\n const command = tool.command ?? tool.name;\n const timeout = tool.timeout;\n const format = tool.formatResponse ?? mcpJson;\n\n server.registerTool(tool.name, { description: tool.description, inputSchema: schema }, async (params: any) => {\n try {\n if (tool.validate) tool.validate(params);\n const result = await sendCommand(command, params, timeout);\n return format(result);\n } catch (e) {\n return mcpError(`${tool.name} error`, e);\n }\n });\n }\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { z } from \"zod\";\n\n/** Function signature for sending commands to Figma via WebSocket */\nexport type SendCommandFn = (command: string, params?: unknown, timeoutMs?: number) => Promise<unknown>;\n\n/** Re-export McpServer type for tool files */\nexport type { McpServer };\n\n// ─── Tool Registry Types ────────────────────────────────────────\n\n/** Access tier for a tool */\nexport type ToolTier = \"read\" | \"create\" | \"edit\";\n\n/** Which tiers are enabled for this MCP session */\nexport interface Capabilities { create: boolean; edit: boolean }\n\n/** Declarative tool definition — replaces imperative registerMcpTools() */\nexport interface ToolDef {\n name: string;\n description: string;\n schema: Record<string, z.ZodTypeAny>\n | ((caps: Capabilities) => Record<string, z.ZodTypeAny>);\n tier: ToolTier;\n /** Figma command name. Defaults to name. */\n command?: string;\n /** sendCommand timeout in ms (default: 30 000) */\n timeout?: number;\n /** Pre-send validation (e.g. per-method item parsing for endpoints) */\n validate?: (params: any) => void;\n /** Custom response formatter. Default: mcpJson */\n formatResponse?: (result: unknown) => any;\n}\n\n/** Standard batch result from Figma handlers */\nexport interface BatchResult<T = unknown> {\n results: Array<T | \"ok\" | { error: string }>;\n warnings?: string[];\n /** Set when some items were deferred (e.g. font loading cap exceeded) */\n deferred?: string;\n}\n\n/** Max response size in characters (~12K tokens). Prevents LLM client-side truncation that corrupts JSON. */\nconst MAX_RESPONSE_CHARS = 50_000;\n\n/** Format a successful MCP response (JSON). Returns a clean error if response exceeds safe size. */\nexport function mcpJson(data: unknown) {\n const text = JSON.stringify(data);\n if (text.length <= MAX_RESPONSE_CHARS) {\n return { content: [{ type: \"text\" as const, text }] };\n }\n return {\n content: [{\n type: \"text\" as const,\n text: JSON.stringify({\n _error: \"response_too_large\",\n _sizeKB: Math.round(text.length / 1024),\n warning: \"Response exceeds safe size. Use 'depth', 'fields', 'limit', or 'summaryOnly' parameters to reduce response size.\",\n }),\n }],\n };\n}\n\n/** Format an error MCP response */\nexport function mcpError(prefix: string, error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return { content: [{ type: \"text\" as const, text: `${prefix}: ${msg}` }] };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC2CA,IAAM,qBAAqB;AAGpB,SAAS,QAAQ,MAAe;AACrC,QAAM,OAAO,KAAK,UAAU,IAAI;AAChC,MAAI,KAAK,UAAU,oBAAoB;AACrC,WAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,KAAK,CAAC,EAAE;AAAA,EACtD;AACA,SAAO;AAAA,IACL,SAAS,CAAC;AAAA,MACR,MAAM;AAAA,MACN,MAAM,KAAK,UAAU;AAAA,QACnB,QAAQ;AAAA,QACR,SAAS,KAAK,MAAM,KAAK,SAAS,IAAI;AAAA,QACtC,SAAS;AAAA,MACX,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;AAGO,SAAS,SAAS,QAAgB,OAAgB;AACvD,QAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,SAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,GAAG,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE;AAC3E;;;ADzDO,SAAS,cACd,QACA,aACA,MACA,OACM;AACN,aAAW,QAAQ,OAAO;AAExB,QAAI,KAAK,SAAS,YAAY,CAAC,KAAK,OAAQ;AAC5C,QAAI,KAAK,SAAS,UAAU,CAAC,KAAK,KAAM;AAExC,UAAM,SAAS,OAAO,KAAK,WAAW,aAAa,KAAK,OAAO,IAAI,IAAI,KAAK;AAC5E,UAAM,UAAU,KAAK,WAAW,KAAK;AACrC,UAAM,UAAU,KAAK;AACrB,UAAM,SAAS,KAAK,kBAAkB;AAEtC,WAAO,aAAa,KAAK,MAAM,EAAE,aAAa,KAAK,aAAa,aAAa,OAAO,GAAG,OAAO,WAAgB;AAC5G,UAAI;AACF,YAAI,KAAK,SAAU,MAAK,SAAS,MAAM;AACvC,cAAM,SAAS,MAAM,YAAY,SAAS,QAAQ,OAAO;AACzD,eAAO,OAAO,MAAM;AAAA,MACtB,SAAS,GAAG;AACV,eAAO,SAAS,GAAG,KAAK,IAAI,UAAU,CAAC;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SendCommandFn, Capabilities, ToolDef } from './types.cjs';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Batch-register declarative ToolDefs on the MCP server.
|
|
7
|
+
*
|
|
8
|
+
* 1. Filters by tier: read → always, create → caps.create, edit → caps.edit
|
|
9
|
+
* 2. Resolves dynamic schemas (endpoint tools pass caps-dependent functions)
|
|
10
|
+
* 3. Generates a uniform handler: validate? → sendCommand → formatResponse ?? mcpJson
|
|
11
|
+
*/
|
|
12
|
+
declare function registerTools(server: McpServer, sendCommand: SendCommandFn, caps: Capabilities, tools: ToolDef[]): void;
|
|
13
|
+
|
|
14
|
+
export { registerTools };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SendCommandFn, Capabilities, ToolDef } from './types.js';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Batch-register declarative ToolDefs on the MCP server.
|
|
7
|
+
*
|
|
8
|
+
* 1. Filters by tier: read → always, create → caps.create, edit → caps.edit
|
|
9
|
+
* 2. Resolves dynamic schemas (endpoint tools pass caps-dependent functions)
|
|
10
|
+
* 3. Generates a uniform handler: validate? → sendCommand → formatResponse ?? mcpJson
|
|
11
|
+
*/
|
|
12
|
+
declare function registerTools(server: McpServer, sendCommand: SendCommandFn, caps: Capabilities, tools: ToolDef[]): void;
|
|
13
|
+
|
|
14
|
+
export { registerTools };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/tools/types.ts
|
|
2
|
+
var MAX_RESPONSE_CHARS = 5e4;
|
|
3
|
+
function mcpJson(data) {
|
|
4
|
+
const text = JSON.stringify(data);
|
|
5
|
+
if (text.length <= MAX_RESPONSE_CHARS) {
|
|
6
|
+
return { content: [{ type: "text", text }] };
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
content: [{
|
|
10
|
+
type: "text",
|
|
11
|
+
text: JSON.stringify({
|
|
12
|
+
_error: "response_too_large",
|
|
13
|
+
_sizeKB: Math.round(text.length / 1024),
|
|
14
|
+
warning: "Response exceeds safe size. Use 'depth', 'fields', 'limit', or 'summaryOnly' parameters to reduce response size."
|
|
15
|
+
})
|
|
16
|
+
}]
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function mcpError(prefix, error) {
|
|
20
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
21
|
+
return { content: [{ type: "text", text: `${prefix}: ${msg}` }] };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/tools/registry.ts
|
|
25
|
+
function registerTools(server, sendCommand, caps, tools) {
|
|
26
|
+
for (const tool of tools) {
|
|
27
|
+
if (tool.tier === "create" && !caps.create) continue;
|
|
28
|
+
if (tool.tier === "edit" && !caps.edit) continue;
|
|
29
|
+
const schema = typeof tool.schema === "function" ? tool.schema(caps) : tool.schema;
|
|
30
|
+
const command = tool.command ?? tool.name;
|
|
31
|
+
const timeout = tool.timeout;
|
|
32
|
+
const format = tool.formatResponse ?? mcpJson;
|
|
33
|
+
server.registerTool(tool.name, { description: tool.description, inputSchema: schema }, async (params) => {
|
|
34
|
+
try {
|
|
35
|
+
if (tool.validate) tool.validate(params);
|
|
36
|
+
const result = await sendCommand(command, params, timeout);
|
|
37
|
+
return format(result);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return mcpError(`${tool.name} error`, e);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
registerTools
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tools/types.ts","../../src/tools/registry.ts"],"sourcesContent":["import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { z } from \"zod\";\n\n/** Function signature for sending commands to Figma via WebSocket */\nexport type SendCommandFn = (command: string, params?: unknown, timeoutMs?: number) => Promise<unknown>;\n\n/** Re-export McpServer type for tool files */\nexport type { McpServer };\n\n// ─── Tool Registry Types ────────────────────────────────────────\n\n/** Access tier for a tool */\nexport type ToolTier = \"read\" | \"create\" | \"edit\";\n\n/** Which tiers are enabled for this MCP session */\nexport interface Capabilities { create: boolean; edit: boolean }\n\n/** Declarative tool definition — replaces imperative registerMcpTools() */\nexport interface ToolDef {\n name: string;\n description: string;\n schema: Record<string, z.ZodTypeAny>\n | ((caps: Capabilities) => Record<string, z.ZodTypeAny>);\n tier: ToolTier;\n /** Figma command name. Defaults to name. */\n command?: string;\n /** sendCommand timeout in ms (default: 30 000) */\n timeout?: number;\n /** Pre-send validation (e.g. per-method item parsing for endpoints) */\n validate?: (params: any) => void;\n /** Custom response formatter. Default: mcpJson */\n formatResponse?: (result: unknown) => any;\n}\n\n/** Standard batch result from Figma handlers */\nexport interface BatchResult<T = unknown> {\n results: Array<T | \"ok\" | { error: string }>;\n warnings?: string[];\n /** Set when some items were deferred (e.g. font loading cap exceeded) */\n deferred?: string;\n}\n\n/** Max response size in characters (~12K tokens). Prevents LLM client-side truncation that corrupts JSON. */\nconst MAX_RESPONSE_CHARS = 50_000;\n\n/** Format a successful MCP response (JSON). Returns a clean error if response exceeds safe size. */\nexport function mcpJson(data: unknown) {\n const text = JSON.stringify(data);\n if (text.length <= MAX_RESPONSE_CHARS) {\n return { content: [{ type: \"text\" as const, text }] };\n }\n return {\n content: [{\n type: \"text\" as const,\n text: JSON.stringify({\n _error: \"response_too_large\",\n _sizeKB: Math.round(text.length / 1024),\n warning: \"Response exceeds safe size. Use 'depth', 'fields', 'limit', or 'summaryOnly' parameters to reduce response size.\",\n }),\n }],\n };\n}\n\n/** Format an error MCP response */\nexport function mcpError(prefix: string, error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return { content: [{ type: \"text\" as const, text: `${prefix}: ${msg}` }] };\n}\n","import type { McpServer, SendCommandFn, Capabilities, ToolDef } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\n\n/**\n * Batch-register declarative ToolDefs on the MCP server.\n *\n * 1. Filters by tier: read → always, create → caps.create, edit → caps.edit\n * 2. Resolves dynamic schemas (endpoint tools pass caps-dependent functions)\n * 3. Generates a uniform handler: validate? → sendCommand → formatResponse ?? mcpJson\n */\nexport function registerTools(\n server: McpServer,\n sendCommand: SendCommandFn,\n caps: Capabilities,\n tools: ToolDef[],\n): void {\n for (const tool of tools) {\n // Tier gate\n if (tool.tier === \"create\" && !caps.create) continue;\n if (tool.tier === \"edit\" && !caps.edit) continue;\n\n const schema = typeof tool.schema === \"function\" ? tool.schema(caps) : tool.schema;\n const command = tool.command ?? tool.name;\n const timeout = tool.timeout;\n const format = tool.formatResponse ?? mcpJson;\n\n server.registerTool(tool.name, { description: tool.description, inputSchema: schema }, async (params: any) => {\n try {\n if (tool.validate) tool.validate(params);\n const result = await sendCommand(command, params, timeout);\n return format(result);\n } catch (e) {\n return mcpError(`${tool.name} error`, e);\n }\n });\n }\n}\n"],"mappings":";AA2CA,IAAM,qBAAqB;AAGpB,SAAS,QAAQ,MAAe;AACrC,QAAM,OAAO,KAAK,UAAU,IAAI;AAChC,MAAI,KAAK,UAAU,oBAAoB;AACrC,WAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,KAAK,CAAC,EAAE;AAAA,EACtD;AACA,SAAO;AAAA,IACL,SAAS,CAAC;AAAA,MACR,MAAM;AAAA,MACN,MAAM,KAAK,UAAU;AAAA,QACnB,QAAQ;AAAA,QACR,SAAS,KAAK,MAAM,KAAK,SAAS,IAAI;AAAA,QACtC,SAAS;AAAA,MACX,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;AAGO,SAAS,SAAS,QAAgB,OAAgB;AACvD,QAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,SAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,GAAG,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE;AAC3E;;;ACzDO,SAAS,cACd,QACA,aACA,MACA,OACM;AACN,aAAW,QAAQ,OAAO;AAExB,QAAI,KAAK,SAAS,YAAY,CAAC,KAAK,OAAQ;AAC5C,QAAI,KAAK,SAAS,UAAU,CAAC,KAAK,KAAM;AAExC,UAAM,SAAS,OAAO,KAAK,WAAW,aAAa,KAAK,OAAO,IAAI,IAAI,KAAK;AAC5E,UAAM,UAAU,KAAK,WAAW,KAAK;AACrC,UAAM,UAAU,KAAK;AACrB,UAAM,SAAS,KAAK,kBAAkB;AAEtC,WAAO,aAAa,KAAK,MAAM,EAAE,aAAa,KAAK,aAAa,aAAa,OAAO,GAAG,OAAO,WAAgB;AAC5G,UAAI;AACF,YAAI,KAAK,SAAU,MAAK,SAAS,MAAM;AACvC,cAAM,SAAS,MAAM,YAAY,SAAS,QAAQ,OAAO;AACzD,eAAO,OAAO,MAAM;AAAA,MACtB,SAAS,GAAG;AACV,eAAO,SAAS,GAAG,KAAK,IAAI,UAAU,CAAC;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
|