@tempad-dev/mcp 0.3.4 → 0.3.5

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/hub.mjs ADDED
@@ -0,0 +1,464 @@
1
+ import { PACKAGE_VERSION, RUNTIME_DIR, SOCK_PATH, ensureDir, log } from "./shared.mjs";
2
+ import { getMcpServerConfig } from "./config.mjs";
3
+ import { createAssetHttpServer } from "./asset-http-server.mjs";
4
+ import { createAssetStore } from "./asset-store.mjs";
5
+ import { cleanupAll, cleanupForExtension, register, reject, resolve } from "./request.mjs";
6
+ import { MCP_INSTRUCTIONS, TOOL_DEFS, coercePayloadToToolResponse, createToolErrorResponse } from "./tools.mjs";
7
+ import { createServer } from "node:net";
8
+ import { chmodSync, existsSync, readFileSync, rmSync, statSync } from "node:fs";
9
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
+ import { GetAssetsResultSchema, MCP_ASSET_RESOURCE_NAME, MCP_ASSET_URI_PREFIX, MCP_ASSET_URI_TEMPLATE, MessageFromExtensionSchema } from "@tempad-dev/mcp-shared";
12
+ import { nanoid } from "nanoid";
13
+ import { WebSocketServer } from "ws";
14
+
15
+ //#region src/hub.ts
16
+ const SHUTDOWN_TIMEOUT = 2e3;
17
+ const { wsPortCandidates, toolTimeoutMs, maxPayloadBytes, autoActivateGraceMs } = getMcpServerConfig();
18
+ log.info({ version: PACKAGE_VERSION }, "TemPad MCP Hub starting...");
19
+ const extensions = [];
20
+ let consumerCount = 0;
21
+ let autoActivateTimer = null;
22
+ let selectedWsPort = 0;
23
+ const mcp = new McpServer({
24
+ name: "tempad-dev-mcp",
25
+ version: PACKAGE_VERSION
26
+ }, MCP_INSTRUCTIONS ? { instructions: MCP_INSTRUCTIONS } : void 0);
27
+ function enrichToolDefinition(tool) {
28
+ if (tool.target === "extension") return tool;
29
+ switch (tool.name) {
30
+ case "get_assets": return {
31
+ ...tool,
32
+ handler: handleGetAssets
33
+ };
34
+ default: throw new Error("No handler configured for hub tool.");
35
+ }
36
+ }
37
+ const TOOL_DEFINITIONS = TOOL_DEFS.map((tool) => enrichToolDefinition(tool));
38
+ function hasFormatter(tool) {
39
+ return tool.target === "extension" && "format" in tool;
40
+ }
41
+ const TOOL_BY_NAME = Object.fromEntries(TOOL_DEFINITIONS.map((tool) => [tool.name, tool]));
42
+ function getToolDefinition(name) {
43
+ return TOOL_BY_NAME[name];
44
+ }
45
+ const assetStore = createAssetStore();
46
+ const assetHttpServer = createAssetHttpServer(assetStore);
47
+ await assetHttpServer.start();
48
+ registerAssetResources();
49
+ function registerAssetResources() {
50
+ const template = new ResourceTemplate(MCP_ASSET_URI_TEMPLATE, { list: async () => ({ resources: assetStore.list().filter((record) => existsSync(record.filePath)).map((record) => ({
51
+ uri: buildAssetResourceUri(record.hash),
52
+ name: formatAssetResourceName(record.hash),
53
+ description: `${record.mimeType} (${formatBytes(record.size)})`,
54
+ mimeType: record.mimeType
55
+ })) }) });
56
+ mcp.registerResource(MCP_ASSET_RESOURCE_NAME, template, { description: "Binary assets captured by the TemPad Dev hub." }, async (_uri, variables) => {
57
+ return readAssetResource(typeof variables.hash === "string" ? variables.hash : "");
58
+ });
59
+ }
60
+ async function readAssetResource(hash) {
61
+ if (!hash) throw new Error("Missing asset hash in resource URI.");
62
+ const record = assetStore.get(hash);
63
+ if (!record) throw new Error(`Asset ${hash} not found.`);
64
+ if (!existsSync(record.filePath)) {
65
+ assetStore.remove(hash, { removeFile: false });
66
+ throw new Error(`Asset ${hash} file is missing.`);
67
+ }
68
+ const stat = statSync(record.filePath);
69
+ const estimatedSize = Math.ceil(stat.size / 3) * 4;
70
+ if (estimatedSize > maxPayloadBytes) throw new Error(`Asset ${hash} is too large (${formatBytes(stat.size)}, encoded: ${formatBytes(estimatedSize)}) to read via MCP protocol. Use HTTP download.`);
71
+ assetStore.touch(hash);
72
+ const buffer = readFileSync(record.filePath);
73
+ const resourceUri = buildAssetResourceUri(hash);
74
+ if (isTextualMime(record.mimeType)) return { contents: [{
75
+ uri: resourceUri,
76
+ mimeType: record.mimeType,
77
+ text: buffer.toString("utf8")
78
+ }] };
79
+ return { contents: [{
80
+ uri: resourceUri,
81
+ mimeType: record.mimeType,
82
+ blob: buffer.toString("base64")
83
+ }] };
84
+ }
85
+ function isTextualMime(mimeType) {
86
+ return mimeType === "image/svg+xml" || mimeType.startsWith("text/");
87
+ }
88
+ function buildAssetResourceUri(hash) {
89
+ return `${MCP_ASSET_URI_PREFIX}${hash}`;
90
+ }
91
+ function formatAssetResourceName(hash) {
92
+ return `asset:${hash.slice(0, 8)}`;
93
+ }
94
+ function buildAssetDescriptor(record) {
95
+ return {
96
+ hash: record.hash,
97
+ url: `${assetHttpServer.getBaseUrl()}/assets/${record.hash}`,
98
+ mimeType: record.mimeType,
99
+ size: record.size,
100
+ resourceUri: buildAssetResourceUri(record.hash),
101
+ width: record.metadata?.width,
102
+ height: record.metadata?.height
103
+ };
104
+ }
105
+ function createAssetResourceLinkBlock(asset) {
106
+ return {
107
+ type: "resource_link",
108
+ name: formatAssetResourceName(asset.hash),
109
+ uri: asset.resourceUri,
110
+ mimeType: asset.mimeType,
111
+ description: `${describeAsset(asset)} - Download: ${asset.url}`
112
+ };
113
+ }
114
+ function describeAsset(asset) {
115
+ return `${asset.mimeType} (${formatBytes(asset.size)})`;
116
+ }
117
+ function registerTools() {
118
+ const registered = [];
119
+ for (const tool of TOOL_DEFINITIONS) {
120
+ if ("exposed" in tool && tool.exposed === false) continue;
121
+ registerTool(tool);
122
+ registered.push(tool.name);
123
+ }
124
+ log.info({ tools: registered }, "Registered tools.");
125
+ }
126
+ registerTools();
127
+ function registerTool(tool) {
128
+ if (tool.target === "extension") registerProxiedTool(tool);
129
+ else registerLocalTool(tool);
130
+ }
131
+ function registerProxiedTool(tool) {
132
+ const schema = tool.parameters;
133
+ mcp.registerTool(tool.name, {
134
+ description: tool.description,
135
+ inputSchema: schema
136
+ }, async (args) => {
137
+ try {
138
+ const parsedArgs = schema.parse(args);
139
+ const activeExt = extensions.find((e) => e.active);
140
+ if (!activeExt) throw new Error("No active TemPad Dev extension available.");
141
+ const { promise, requestId } = register(activeExt.id, toolTimeoutMs);
142
+ const message = {
143
+ type: "toolCall",
144
+ id: requestId,
145
+ payload: {
146
+ name: tool.name,
147
+ args: parsedArgs
148
+ }
149
+ };
150
+ activeExt.ws.send(JSON.stringify(message));
151
+ log.info({
152
+ tool: tool.name,
153
+ req: requestId,
154
+ extId: activeExt.id
155
+ }, "Forwarded tool call.");
156
+ const payload = await promise;
157
+ return createToolResponse(tool.name, payload);
158
+ } catch (error) {
159
+ log.error({
160
+ tool: tool.name,
161
+ error
162
+ }, "Tool invocation failed before reaching extension.");
163
+ return createToolErrorResponse(tool.name, error);
164
+ }
165
+ });
166
+ }
167
+ function registerLocalTool(tool) {
168
+ const schema = tool.parameters;
169
+ const handler = tool.handler;
170
+ const registrationOptions = {
171
+ description: tool.description,
172
+ inputSchema: schema
173
+ };
174
+ if (tool.outputSchema) registrationOptions.outputSchema = tool.outputSchema;
175
+ mcp.registerTool(tool.name, registrationOptions, async (args) => {
176
+ try {
177
+ return await handler(schema.parse(args));
178
+ } catch (error) {
179
+ log.error({
180
+ tool: tool.name,
181
+ error
182
+ }, "Local tool invocation failed.");
183
+ return createToolErrorResponse(tool.name, error);
184
+ }
185
+ });
186
+ }
187
+ function createToolResponse(toolName, payload) {
188
+ const definition = getToolDefinition(toolName);
189
+ if (definition && hasFormatter(definition)) try {
190
+ const formatter = definition.format;
191
+ return formatter(payload);
192
+ } catch (error) {
193
+ log.warn({
194
+ tool: toolName,
195
+ error
196
+ }, "Failed to format tool result; returning raw payload.");
197
+ return coercePayloadToToolResponse(payload);
198
+ }
199
+ return coercePayloadToToolResponse(payload);
200
+ }
201
+ async function handleGetAssets({ hashes }) {
202
+ if (hashes.length > 100) throw new Error("Too many hashes requested. Limit is 100.");
203
+ const unique = Array.from(new Set(hashes));
204
+ const records = assetStore.getMany(unique).filter((record) => {
205
+ if (existsSync(record.filePath)) return true;
206
+ assetStore.remove(record.hash, { removeFile: false });
207
+ return false;
208
+ });
209
+ const found = new Set(records.map((record) => record.hash));
210
+ const payload = GetAssetsResultSchema.parse({
211
+ assets: records.map((record) => buildAssetDescriptor(record)),
212
+ missing: unique.filter((hash) => !found.has(hash))
213
+ });
214
+ const summary = [];
215
+ summary.push(payload.assets.length ? `Resolved ${payload.assets.length} asset${payload.assets.length === 1 ? "" : "s"}.` : "No assets were resolved for the requested hashes.");
216
+ if (payload.missing.length) summary.push(`Missing: ${payload.missing.join(", ")}`);
217
+ summary.push("Use resources/read with each resourceUri or fetch the fallback URL to download bytes.");
218
+ return {
219
+ content: [{
220
+ type: "text",
221
+ text: summary.join("\n")
222
+ }, ...payload.assets.map((asset) => createAssetResourceLinkBlock(asset))],
223
+ structuredContent: payload
224
+ };
225
+ }
226
+ function getActiveId() {
227
+ return extensions.find((e) => e.active)?.id ?? null;
228
+ }
229
+ function setActive(targetId) {
230
+ extensions.forEach((e) => {
231
+ e.active = targetId !== null && e.id === targetId;
232
+ });
233
+ }
234
+ function clearAutoActivateTimer() {
235
+ if (autoActivateTimer) {
236
+ clearTimeout(autoActivateTimer);
237
+ autoActivateTimer = null;
238
+ }
239
+ }
240
+ function scheduleAutoActivate() {
241
+ clearAutoActivateTimer();
242
+ if (extensions.length !== 1 || getActiveId()) return;
243
+ const target = extensions[0];
244
+ autoActivateTimer = setTimeout(() => {
245
+ autoActivateTimer = null;
246
+ if (extensions.length === 1 && !getActiveId()) {
247
+ setActive(target.id);
248
+ log.info({ id: target.id }, "Auto-activated sole extension after grace period.");
249
+ broadcastState();
250
+ }
251
+ }, autoActivateGraceMs);
252
+ }
253
+ function unrefTimer(timer) {
254
+ if (typeof timer === "object" && timer !== null) {
255
+ const handle = timer;
256
+ if (typeof handle.unref === "function") handle.unref();
257
+ }
258
+ }
259
+ function broadcastState() {
260
+ const activeId = getActiveId();
261
+ const message = {
262
+ type: "state",
263
+ activeId,
264
+ count: extensions.length,
265
+ port: selectedWsPort,
266
+ assetServerUrl: assetHttpServer.getBaseUrl()
267
+ };
268
+ extensions.forEach((ext) => ext.ws.send(JSON.stringify(message)));
269
+ log.debug({
270
+ activeId,
271
+ count: extensions.length
272
+ }, "Broadcasted state.");
273
+ }
274
+ function rawDataToBuffer(raw) {
275
+ if (typeof raw === "string") return Buffer.from(raw);
276
+ if (Buffer.isBuffer(raw)) return raw;
277
+ if (raw instanceof ArrayBuffer) return Buffer.from(raw);
278
+ return Buffer.concat(raw);
279
+ }
280
+ function formatBytes(bytes) {
281
+ if (bytes < 1024) return `${bytes} B`;
282
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
283
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
284
+ }
285
+ function shutdown() {
286
+ log.info("Hub is shutting down...");
287
+ assetStore.flush();
288
+ assetHttpServer.stop();
289
+ netServer.close(() => log.info("Net server closed."));
290
+ wss?.close(() => log.info("WebSocket server closed."));
291
+ cleanupAll();
292
+ unrefTimer(setTimeout(() => {
293
+ log.warn("Shutdown timed out. Forcing exit.");
294
+ process.exit(1);
295
+ }, SHUTDOWN_TIMEOUT));
296
+ }
297
+ try {
298
+ ensureDir(RUNTIME_DIR);
299
+ if (process.platform !== "win32" && existsSync(SOCK_PATH)) {
300
+ log.warn({ sock: SOCK_PATH }, "Removing stale socket file.");
301
+ rmSync(SOCK_PATH);
302
+ }
303
+ } catch (error) {
304
+ log.error({ err: error }, "Failed to initialize runtime environment.");
305
+ process.exit(1);
306
+ }
307
+ const netServer = createServer((sock) => {
308
+ consumerCount++;
309
+ log.info(`Consumer connected. Total: ${consumerCount}`);
310
+ const transport = new StdioServerTransport(sock, sock);
311
+ mcp.connect(transport).catch((err) => {
312
+ log.error({ err }, "Failed to attach MCP transport.");
313
+ transport.close().catch((closeErr) => log.warn({ err: closeErr }, "Transport close failed."));
314
+ sock.destroy();
315
+ });
316
+ sock.on("error", (err) => {
317
+ log.warn({ err }, "Consumer socket error.");
318
+ transport.close().catch((closeErr) => log.warn({ err: closeErr }, "Transport close failed."));
319
+ });
320
+ sock.on("close", async () => {
321
+ await transport.close();
322
+ consumerCount--;
323
+ log.info(`Consumer disconnected. Remaining: ${consumerCount}`);
324
+ if (consumerCount === 0) {
325
+ log.info("Last consumer disconnected. Shutting down.");
326
+ shutdown();
327
+ }
328
+ });
329
+ });
330
+ netServer.on("error", (err) => {
331
+ log.error({ err }, "Net server error.");
332
+ process.exit(1);
333
+ });
334
+ netServer.listen(SOCK_PATH, () => {
335
+ try {
336
+ if (process.platform !== "win32") chmodSync(SOCK_PATH, 384);
337
+ } catch (err) {
338
+ log.error({ err }, "Failed to set socket permissions. Shutting down.");
339
+ process.exit(1);
340
+ }
341
+ log.info({ sock: SOCK_PATH }, "Hub socket ready.");
342
+ });
343
+ async function startWebSocketServer() {
344
+ for (const candidate of wsPortCandidates) {
345
+ const server = new WebSocketServer({
346
+ host: "127.0.0.1",
347
+ port: candidate,
348
+ maxPayload: maxPayloadBytes
349
+ });
350
+ try {
351
+ await new Promise((resolve$1, reject$1) => {
352
+ const onError = (err) => {
353
+ server.off("listening", onListening);
354
+ reject$1(err);
355
+ };
356
+ const onListening = () => {
357
+ server.off("error", onError);
358
+ resolve$1();
359
+ };
360
+ server.once("error", onError);
361
+ server.once("listening", onListening);
362
+ });
363
+ return {
364
+ wss: server,
365
+ port: candidate
366
+ };
367
+ } catch (err) {
368
+ server.close();
369
+ const errno = err;
370
+ if (errno.code === "EADDRINUSE") {
371
+ log.warn({ port: candidate }, "WebSocket port in use, trying next candidate.");
372
+ continue;
373
+ }
374
+ log.error({
375
+ err: errno,
376
+ port: candidate
377
+ }, "Failed to start WebSocket server.");
378
+ process.exit(1);
379
+ }
380
+ }
381
+ log.error({ candidates: wsPortCandidates }, "Unable to start WebSocket server on any candidate port.");
382
+ process.exit(1);
383
+ }
384
+ const { wss, port } = await startWebSocketServer();
385
+ selectedWsPort = port;
386
+ wss.on("error", (err) => {
387
+ log.error({ err }, "WebSocket server critical error. Exiting.");
388
+ process.exit(1);
389
+ });
390
+ wss.on("connection", (ws) => {
391
+ const ext = {
392
+ id: nanoid(),
393
+ ws,
394
+ active: false
395
+ };
396
+ extensions.push(ext);
397
+ log.info({ id: ext.id }, `Extension connected. Total: ${extensions.length}`);
398
+ const message = {
399
+ type: "registered",
400
+ id: ext.id
401
+ };
402
+ ws.send(JSON.stringify(message));
403
+ broadcastState();
404
+ scheduleAutoActivate();
405
+ ws.on("message", (raw, isBinary) => {
406
+ if (isBinary) {
407
+ log.warn({ extId: ext.id }, "Unexpected binary message received.");
408
+ return;
409
+ }
410
+ const messageBuffer = rawDataToBuffer(raw);
411
+ let parsedJson;
412
+ try {
413
+ parsedJson = JSON.parse(messageBuffer.toString("utf-8"));
414
+ } catch (e) {
415
+ log.warn({
416
+ err: e,
417
+ extId: ext.id
418
+ }, "Failed to parse message.");
419
+ return;
420
+ }
421
+ const parseResult = MessageFromExtensionSchema.safeParse(parsedJson);
422
+ if (!parseResult.success) {
423
+ log.warn({
424
+ error: parseResult.error.flatten(),
425
+ extId: ext.id
426
+ }, "Invalid message shape.");
427
+ return;
428
+ }
429
+ const msg = parseResult.data;
430
+ switch (msg.type) {
431
+ case "activate":
432
+ setActive(ext.id);
433
+ log.info({ id: ext.id }, "Extension activated.");
434
+ broadcastState();
435
+ scheduleAutoActivate();
436
+ break;
437
+ case "toolResult": {
438
+ const { id, payload, error } = msg;
439
+ if (error) reject(id, error instanceof Error ? error : new Error(String(error)));
440
+ else resolve(id, payload);
441
+ break;
442
+ }
443
+ }
444
+ });
445
+ ws.on("close", () => {
446
+ const index = extensions.findIndex((e) => e.id === ext.id);
447
+ if (index > -1) extensions.splice(index, 1);
448
+ log.info({ id: ext.id }, `Extension disconnected. Remaining: ${extensions.length}`);
449
+ cleanupForExtension(ext.id);
450
+ if (ext.active) {
451
+ log.warn({ id: ext.id }, "Active extension disconnected.");
452
+ setActive(null);
453
+ }
454
+ broadcastState();
455
+ scheduleAutoActivate();
456
+ });
457
+ });
458
+ log.info({ port: selectedWsPort }, "WebSocket server ready.");
459
+ process.on("SIGINT", shutdown);
460
+ process.on("SIGTERM", shutdown);
461
+
462
+ //#endregion
463
+ export { };
464
+ //# sourceMappingURL=hub.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hub.mjs","names":["extensions: ExtensionConnection[]","autoActivateTimer: TimeoutHandle | null","TOOL_DEFINITIONS: ReadonlyArray<RegisteredToolDefinition>","TOOL_BY_NAME: ToolDefinitionByName","registered: string[]","message: ToolCallMessage","registrationOptions: {\n description: string\n inputSchema: McpInputSchema\n outputSchema?: McpOutputSchema\n }","payload: GetAssetsResult","summary: string[]","message: StateMessage","error: unknown","ext: ExtensionConnection","message: RegisteredMessage","parsedJson: unknown","e: unknown"],"sources":["../src/hub.ts"],"sourcesContent":["import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport type {\n AssetDescriptor,\n GetAssetsParametersInput,\n GetAssetsResult,\n RegisteredMessage,\n StateMessage,\n ToolCallMessage,\n ToolName,\n ToolResultMap,\n ToolResultMessage\n} from '@tempad-dev/mcp-shared'\nimport type { RawData } from 'ws'\nimport type { ZodType } from 'zod'\n\nimport { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport {\n GetAssetsResultSchema,\n MCP_ASSET_RESOURCE_NAME,\n MCP_ASSET_URI_PREFIX,\n MCP_ASSET_URI_TEMPLATE,\n MessageFromExtensionSchema\n} from '@tempad-dev/mcp-shared'\nimport { nanoid } from 'nanoid'\nimport { existsSync, rmSync, chmodSync, readFileSync, statSync } from 'node:fs'\nimport { createServer } from 'node:net'\nimport { WebSocketServer } from 'ws'\n\nimport type { AssetRecord, ExtensionConnection } from './types'\n\nimport { createAssetHttpServer } from './asset-http-server'\nimport { createAssetStore } from './asset-store'\nimport { getMcpServerConfig } from './config'\nimport { register, resolve, reject, cleanupForExtension, cleanupAll } from './request'\nimport { PACKAGE_VERSION, log, RUNTIME_DIR, SOCK_PATH, ensureDir } from './shared'\nimport {\n TOOL_DEFS,\n MCP_INSTRUCTIONS,\n coercePayloadToToolResponse,\n createToolErrorResponse\n} from './tools'\n\nconst SHUTDOWN_TIMEOUT = 2000\nconst { wsPortCandidates, toolTimeoutMs, maxPayloadBytes, autoActivateGraceMs } =\n getMcpServerConfig()\n\nlog.info({ version: PACKAGE_VERSION }, 'TemPad MCP Hub starting...')\n\nconst extensions: ExtensionConnection[] = []\nlet consumerCount = 0\ntype TimeoutHandle = ReturnType<typeof setTimeout>\nlet autoActivateTimer: TimeoutHandle | null = null\nlet selectedWsPort = 0\n\nconst mcp = new McpServer(\n { name: 'tempad-dev-mcp', version: PACKAGE_VERSION },\n MCP_INSTRUCTIONS ? { instructions: MCP_INSTRUCTIONS } : undefined\n)\ntype McpInputSchema = Parameters<typeof mcp.registerTool>[1]['inputSchema']\ntype McpOutputSchema = Parameters<typeof mcp.registerTool>[1]['outputSchema']\ntype ToolResponse = CallToolResult\ntype SchemaOutput<Schema extends ZodType> = Schema['_output']\ntype ToolMetadataEntry = (typeof TOOL_DEFS)[number]\ntype ExtensionToolMetadata = Extract<ToolMetadataEntry, { target: 'extension' }>\ntype HubToolMetadata = Extract<ToolMetadataEntry, { target: 'hub' }>\n\ntype HubToolWithHandler<T extends HubToolMetadata = HubToolMetadata> = T & {\n handler: (args: SchemaOutput<T['parameters']>) => Promise<ToolResponse>\n}\n\ntype RegisteredToolDefinition = ExtensionToolMetadata | HubToolWithHandler\n\nfunction enrichToolDefinition(tool: ToolMetadataEntry): RegisteredToolDefinition {\n if (tool.target === 'extension') {\n return tool\n }\n\n switch (tool.name) {\n case 'get_assets':\n return {\n ...tool,\n handler: handleGetAssets\n } satisfies HubToolWithHandler\n default:\n throw new Error('No handler configured for hub tool.')\n }\n}\n\nconst TOOL_DEFINITIONS: ReadonlyArray<RegisteredToolDefinition> = TOOL_DEFS.map((tool) =>\n enrichToolDefinition(tool)\n)\n\ntype RegisteredTool = (typeof TOOL_DEFINITIONS)[number]\ntype ExtensionTool = Extract<RegisteredTool, { target: 'extension' }>\ntype HubOnlyTool = Extract<RegisteredTool, { target: 'hub' }>\n\nfunction hasFormatter(tool: RegisteredToolDefinition): tool is ExtensionTool & {\n format: (payload: unknown) => ToolResponse\n} {\n return tool.target === 'extension' && 'format' in tool\n}\n\ntype ToolDefinitionByName = {\n [T in RegisteredToolDefinition as T['name']]: T\n}\n\nconst TOOL_BY_NAME: ToolDefinitionByName = Object.fromEntries(\n TOOL_DEFINITIONS.map((tool) => [tool.name, tool] as const)\n) as ToolDefinitionByName\n\nfunction getToolDefinition<Name extends ToolName>(name: Name): ToolDefinitionByName[Name] {\n return TOOL_BY_NAME[name]\n}\n\nconst assetStore = createAssetStore()\nconst assetHttpServer = createAssetHttpServer(assetStore)\nawait assetHttpServer.start()\nregisterAssetResources()\n\nfunction registerAssetResources(): void {\n const template = new ResourceTemplate(MCP_ASSET_URI_TEMPLATE, {\n list: async () => ({\n resources: assetStore\n .list()\n .filter((record) => existsSync(record.filePath))\n .map((record) => ({\n uri: buildAssetResourceUri(record.hash),\n name: formatAssetResourceName(record.hash),\n description: `${record.mimeType} (${formatBytes(record.size)})`,\n mimeType: record.mimeType\n }))\n })\n })\n\n mcp.registerResource(\n MCP_ASSET_RESOURCE_NAME,\n template,\n {\n description: 'Binary assets captured by the TemPad Dev hub.'\n },\n async (_uri, variables) => {\n const hash = typeof variables.hash === 'string' ? variables.hash : ''\n return readAssetResource(hash)\n }\n )\n}\n\nasync function readAssetResource(hash: string) {\n if (!hash) {\n throw new Error('Missing asset hash in resource URI.')\n }\n const record = assetStore.get(hash)\n if (!record) {\n throw new Error(`Asset ${hash} not found.`)\n }\n\n if (!existsSync(record.filePath)) {\n assetStore.remove(hash, { removeFile: false })\n throw new Error(`Asset ${hash} file is missing.`)\n }\n\n const stat = statSync(record.filePath)\n // Base64 encoding increases size by ~33% (4 bytes for every 3 bytes)\n const estimatedSize = Math.ceil(stat.size / 3) * 4\n if (estimatedSize > maxPayloadBytes) {\n throw new Error(\n `Asset ${hash} is too large (${formatBytes(stat.size)}, encoded: ${formatBytes(estimatedSize)}) to read via MCP protocol. Use HTTP download.`\n )\n }\n\n assetStore.touch(hash)\n const buffer = readFileSync(record.filePath)\n const resourceUri = buildAssetResourceUri(hash)\n\n if (isTextualMime(record.mimeType)) {\n return {\n contents: [\n {\n uri: resourceUri,\n mimeType: record.mimeType,\n text: buffer.toString('utf8')\n }\n ]\n }\n }\n\n return {\n contents: [\n {\n uri: resourceUri,\n mimeType: record.mimeType,\n blob: buffer.toString('base64')\n }\n ]\n }\n}\n\nfunction isTextualMime(mimeType: string): boolean {\n return mimeType === 'image/svg+xml' || mimeType.startsWith('text/')\n}\n\nfunction buildAssetResourceUri(hash: string): string {\n return `${MCP_ASSET_URI_PREFIX}${hash}`\n}\n\nfunction formatAssetResourceName(hash: string): string {\n return `asset:${hash.slice(0, 8)}`\n}\n\nfunction buildAssetDescriptor(record: AssetRecord): AssetDescriptor {\n return {\n hash: record.hash,\n url: `${assetHttpServer.getBaseUrl()}/assets/${record.hash}`,\n mimeType: record.mimeType,\n size: record.size,\n resourceUri: buildAssetResourceUri(record.hash),\n width: record.metadata?.width,\n height: record.metadata?.height\n }\n}\n\nfunction createAssetResourceLinkBlock(asset: AssetDescriptor) {\n return {\n type: 'resource_link' as const,\n name: formatAssetResourceName(asset.hash),\n uri: asset.resourceUri,\n mimeType: asset.mimeType,\n description: `${describeAsset(asset)} - Download: ${asset.url}`\n }\n}\n\nfunction describeAsset(asset: AssetDescriptor): string {\n return `${asset.mimeType} (${formatBytes(asset.size)})`\n}\n\nfunction registerTools(): void {\n const registered: string[] = []\n for (const tool of TOOL_DEFINITIONS) {\n if ('exposed' in tool && tool.exposed === false) continue\n registerTool(tool)\n registered.push(tool.name)\n }\n log.info({ tools: registered }, 'Registered tools.')\n}\n\nregisterTools()\nfunction registerTool(tool: RegisteredTool): void {\n if (tool.target === 'extension') {\n registerProxiedTool(tool)\n } else {\n registerLocalTool(tool)\n }\n}\n\nfunction registerProxiedTool<T extends ExtensionTool>(tool: T): void {\n type Name = T['name']\n type Result = ToolResultMap[Name]\n\n const schema = tool.parameters\n mcp.registerTool(\n tool.name,\n {\n description: tool.description,\n inputSchema: schema as unknown as McpInputSchema\n },\n async (args: unknown) => {\n try {\n const parsedArgs = schema.parse(args)\n const activeExt = extensions.find((e) => e.active)\n if (!activeExt) throw new Error('No active TemPad Dev extension available.')\n\n const { promise, requestId } = register<Result>(activeExt.id, toolTimeoutMs)\n\n const message: ToolCallMessage = {\n type: 'toolCall',\n id: requestId,\n payload: {\n name: tool.name,\n args: parsedArgs\n }\n }\n activeExt.ws.send(JSON.stringify(message))\n log.info({ tool: tool.name, req: requestId, extId: activeExt.id }, 'Forwarded tool call.')\n\n const payload = await promise\n return createToolResponse(tool.name, payload)\n } catch (error) {\n log.error({ tool: tool.name, error }, 'Tool invocation failed before reaching extension.')\n return createToolErrorResponse(tool.name, error)\n }\n }\n )\n}\n\nfunction registerLocalTool(tool: HubOnlyTool): void {\n const schema = tool.parameters\n const handler = tool.handler\n\n const registrationOptions: {\n description: string\n inputSchema: McpInputSchema\n outputSchema?: McpOutputSchema\n } = {\n description: tool.description,\n inputSchema: schema as unknown as McpInputSchema\n }\n\n if (tool.outputSchema) {\n registrationOptions.outputSchema = tool.outputSchema as unknown as McpOutputSchema\n }\n\n mcp.registerTool(tool.name, registrationOptions, async (args: unknown) => {\n try {\n const parsed = schema.parse(args)\n return await handler(parsed)\n } catch (error) {\n log.error({ tool: tool.name, error }, 'Local tool invocation failed.')\n return createToolErrorResponse(tool.name, error)\n }\n })\n}\n\nfunction createToolResponse<Name extends ToolName>(\n toolName: Name,\n payload: ToolResultMap[Name]\n): ToolResponse {\n const definition = getToolDefinition(toolName)\n if (definition && hasFormatter(definition)) {\n try {\n const formatter = definition.format as (input: ToolResultMap[Name]) => ToolResponse\n return formatter(payload)\n } catch (error) {\n log.warn({ tool: toolName, error }, 'Failed to format tool result; returning raw payload.')\n return coercePayloadToToolResponse(payload)\n }\n }\n\n return coercePayloadToToolResponse(payload)\n}\n\nasync function handleGetAssets({ hashes }: GetAssetsParametersInput): Promise<ToolResponse> {\n if (hashes.length > 100) {\n throw new Error('Too many hashes requested. Limit is 100.')\n }\n const unique = Array.from(new Set(hashes))\n const records = assetStore.getMany(unique).filter((record) => {\n if (existsSync(record.filePath)) return true\n assetStore.remove(record.hash, { removeFile: false })\n return false\n })\n const found = new Set(records.map((record) => record.hash))\n const payload: GetAssetsResult = GetAssetsResultSchema.parse({\n assets: records.map((record) => buildAssetDescriptor(record)),\n missing: unique.filter((hash) => !found.has(hash))\n })\n\n const summary: string[] = []\n summary.push(\n payload.assets.length\n ? `Resolved ${payload.assets.length} asset${payload.assets.length === 1 ? '' : 's'}.`\n : 'No assets were resolved for the requested hashes.'\n )\n if (payload.missing.length) {\n summary.push(`Missing: ${payload.missing.join(', ')}`)\n }\n summary.push(\n 'Use resources/read with each resourceUri or fetch the fallback URL to download bytes.'\n )\n\n const content = [\n {\n type: 'text' as const,\n text: summary.join('\\n')\n },\n ...payload.assets.map((asset) => createAssetResourceLinkBlock(asset))\n ]\n\n return {\n content,\n structuredContent: payload\n }\n}\n\nfunction getActiveId(): string | null {\n return extensions.find((e) => e.active)?.id ?? null\n}\n\nfunction setActive(targetId: string | null): void {\n extensions.forEach((e) => {\n e.active = targetId !== null && e.id === targetId\n })\n}\n\nfunction clearAutoActivateTimer(): void {\n if (autoActivateTimer) {\n clearTimeout(autoActivateTimer)\n autoActivateTimer = null\n }\n}\n\nfunction scheduleAutoActivate(): void {\n clearAutoActivateTimer()\n\n if (extensions.length !== 1 || getActiveId()) {\n return\n }\n\n const target = extensions[0]\n autoActivateTimer = setTimeout(() => {\n autoActivateTimer = null\n if (extensions.length === 1 && !getActiveId()) {\n setActive(target.id)\n log.info({ id: target.id }, 'Auto-activated sole extension after grace period.')\n broadcastState()\n }\n }, autoActivateGraceMs)\n}\n\nfunction unrefTimer(timer: TimeoutHandle): void {\n if (typeof timer === 'object' && timer !== null) {\n const handle = timer as NodeJS.Timeout\n if (typeof handle.unref === 'function') {\n handle.unref()\n }\n }\n}\n\nfunction broadcastState(): void {\n const activeId = getActiveId()\n const message: StateMessage = {\n type: 'state',\n activeId,\n count: extensions.length,\n port: selectedWsPort,\n assetServerUrl: assetHttpServer.getBaseUrl()\n }\n extensions.forEach((ext) => ext.ws.send(JSON.stringify(message)))\n log.debug({ activeId, count: extensions.length }, 'Broadcasted state.')\n}\n\nfunction rawDataToBuffer(raw: RawData): Buffer {\n if (typeof raw === 'string') return Buffer.from(raw)\n if (Buffer.isBuffer(raw)) return raw\n if (raw instanceof ArrayBuffer) return Buffer.from(raw)\n return Buffer.concat(raw)\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nfunction shutdown(): void {\n log.info('Hub is shutting down...')\n assetStore.flush()\n assetHttpServer.stop()\n netServer.close(() => log.info('Net server closed.'))\n wss?.close(() => log.info('WebSocket server closed.'))\n cleanupAll()\n const timer = setTimeout(() => {\n log.warn('Shutdown timed out. Forcing exit.')\n process.exit(1)\n }, SHUTDOWN_TIMEOUT)\n unrefTimer(timer)\n}\n\ntry {\n ensureDir(RUNTIME_DIR)\n if (process.platform !== 'win32' && existsSync(SOCK_PATH)) {\n log.warn({ sock: SOCK_PATH }, 'Removing stale socket file.')\n rmSync(SOCK_PATH)\n }\n} catch (error: unknown) {\n log.error({ err: error }, 'Failed to initialize runtime environment.')\n process.exit(1)\n}\n\nconst netServer = createServer((sock) => {\n consumerCount++\n log.info(`Consumer connected. Total: ${consumerCount}`)\n const transport = new StdioServerTransport(sock, sock)\n mcp.connect(transport).catch((err) => {\n log.error({ err }, 'Failed to attach MCP transport.')\n transport.close().catch((closeErr) => log.warn({ err: closeErr }, 'Transport close failed.'))\n sock.destroy()\n })\n sock.on('error', (err) => {\n log.warn({ err }, 'Consumer socket error.')\n transport.close().catch((closeErr) => log.warn({ err: closeErr }, 'Transport close failed.'))\n })\n sock.on('close', async () => {\n await transport.close()\n consumerCount--\n log.info(`Consumer disconnected. Remaining: ${consumerCount}`)\n if (consumerCount === 0) {\n log.info('Last consumer disconnected. Shutting down.')\n shutdown()\n }\n })\n})\nnetServer.on('error', (err) => {\n log.error({ err }, 'Net server error.')\n process.exit(1)\n})\nnetServer.listen(SOCK_PATH, () => {\n try {\n if (process.platform !== 'win32') chmodSync(SOCK_PATH, 0o600)\n } catch (err) {\n log.error({ err }, 'Failed to set socket permissions. Shutting down.')\n process.exit(1)\n }\n log.info({ sock: SOCK_PATH }, 'Hub socket ready.')\n})\n\nasync function startWebSocketServer(): Promise<{ wss: WebSocketServer; port: number }> {\n for (const candidate of wsPortCandidates) {\n const server = new WebSocketServer({\n host: '127.0.0.1',\n port: candidate,\n maxPayload: maxPayloadBytes\n })\n\n try {\n await new Promise<void>((resolve, reject) => {\n const onError = (err: NodeJS.ErrnoException) => {\n server.off('listening', onListening)\n reject(err)\n }\n const onListening = () => {\n server.off('error', onError)\n resolve()\n }\n server.once('error', onError)\n server.once('listening', onListening)\n })\n return { wss: server, port: candidate }\n } catch (err) {\n server.close()\n const errno = err as NodeJS.ErrnoException\n if (errno.code === 'EADDRINUSE') {\n log.warn({ port: candidate }, 'WebSocket port in use, trying next candidate.')\n continue\n }\n log.error({ err: errno, port: candidate }, 'Failed to start WebSocket server.')\n process.exit(1)\n }\n }\n\n log.error(\n { candidates: wsPortCandidates },\n 'Unable to start WebSocket server on any candidate port.'\n )\n process.exit(1)\n}\n\nconst { wss, port } = await startWebSocketServer()\nselectedWsPort = port\n\n// Add an error handler to prevent crashes from port conflicts, etc.\nwss.on('error', (err) => {\n log.error({ err }, 'WebSocket server critical error. Exiting.')\n process.exit(1)\n})\n\nwss.on('connection', (ws) => {\n const ext: ExtensionConnection = { id: nanoid(), ws, active: false }\n extensions.push(ext)\n log.info({ id: ext.id }, `Extension connected. Total: ${extensions.length}`)\n\n const message: RegisteredMessage = { type: 'registered', id: ext.id }\n ws.send(JSON.stringify(message))\n broadcastState()\n scheduleAutoActivate()\n\n ws.on('message', (raw: RawData, isBinary: boolean) => {\n if (isBinary) {\n log.warn({ extId: ext.id }, 'Unexpected binary message received.')\n return\n }\n\n const messageBuffer = rawDataToBuffer(raw)\n\n let parsedJson: unknown\n try {\n parsedJson = JSON.parse(messageBuffer.toString('utf-8'))\n } catch (e: unknown) {\n log.warn({ err: e, extId: ext.id }, 'Failed to parse message.')\n return\n }\n\n const parseResult = MessageFromExtensionSchema.safeParse(parsedJson)\n if (!parseResult.success) {\n log.warn({ error: parseResult.error.flatten(), extId: ext.id }, 'Invalid message shape.')\n return\n }\n const msg = parseResult.data\n\n switch (msg.type) {\n case 'activate': {\n setActive(ext.id)\n log.info({ id: ext.id }, 'Extension activated.')\n broadcastState()\n scheduleAutoActivate()\n break\n }\n case 'toolResult': {\n const { id, payload, error } = msg as ToolResultMessage\n if (error) {\n reject(id, error instanceof Error ? error : new Error(String(error)))\n } else {\n resolve(id, payload)\n }\n break\n }\n }\n })\n\n ws.on('close', () => {\n const index = extensions.findIndex((e) => e.id === ext.id)\n if (index > -1) extensions.splice(index, 1)\n\n log.info({ id: ext.id }, `Extension disconnected. Remaining: ${extensions.length}`)\n cleanupForExtension(ext.id)\n\n if (ext.active) {\n log.warn({ id: ext.id }, 'Active extension disconnected.')\n setActive(null)\n }\n\n broadcastState()\n scheduleAutoActivate()\n })\n})\n\nlog.info({ port: selectedWsPort }, 'WebSocket server ready.')\n\nprocess.on('SIGINT', shutdown)\nprocess.on('SIGTERM', shutdown)\n"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ You are connected to a Figma design file via the MCP server. Convert design elements into production code, preserving design intent while fitting the user’s codebase conventions.
2
+
3
+ - Start from `get_code` as the baseline, then refactor to match project conventions (components, styling system, file structure, naming).
4
+ - Layout confidence:
5
+ - If `get_code` contains no `data-hint-auto-layout`, it likely indicates the layout is explicit. You can be more confident implementing directly from `get_code`.
6
+ - If any `data-hint-auto-layout` is `none` or `inferred`, the corresponding layout may be uncertain. If you feel ambiguity or uncertainty, consult `get_structure` (hierarchy + geometry) and `get_screenshot` (visual intent such as layering/overlap/masks/shadows/translucency).
7
+ - If `data-hint-component` plus repetition supports it, extract reusable components/variants aligned with project patterns. Do not preserve hint strings in output.
8
+ - Tokens: follow the project’s token/theming framework; if needed, use `get_code.usedToken` metadata (collection, mode) to extend a responsive token system within that framework.
9
+ - Assets: follow the project’s existing conventions/practices (icon system, asset pipeline, import/path rules, optimization) to decide how to represent and reference assets. If `get_code` uses resource URIs, you may replace them with the project’s canonical references when appropriate without changing rendering.
10
+ - Do not output any `data-*` attributes returned by `get_code`.
11
+ - For SVG/vector assets: use the exact provided asset, preserving `path` data, `viewBox`, and full SVG structure. Never redraw or approximate vectors.
@@ -0,0 +1,38 @@
1
+ //#region package.json
2
+ var package_default = {
3
+ name: "@tempad-dev/mcp",
4
+ description: "MCP server for TemPad Dev.",
5
+ version: "0.3.5",
6
+ type: "module",
7
+ main: "dist/cli.js",
8
+ bin: "dist/cli.js",
9
+ files: ["dist/**/*", "README.md"],
10
+ scripts: {
11
+ "build": "tsdown && cp src/instructions.md dist/instructions.md",
12
+ "typecheck": "tsc -p tsconfig.json --noEmit",
13
+ "lint": "eslint . --ext .ts,.mts,.cts,.js,.mjs,.cjs",
14
+ "format": "prettier --write \"**/*.{js,ts,mjs,cjs,cts,mts,json,md}\"",
15
+ "prepublishOnly": "pnpm run build"
16
+ },
17
+ dependencies: {
18
+ "@tempad-dev/mcp-shared": "workspace:^0.1.0",
19
+ "@modelcontextprotocol/sdk": "^1.24.0",
20
+ "nanoid": "^5.1.6",
21
+ "pino": "^9.14.0",
22
+ "pino-pretty": "^11.2.2",
23
+ "proper-lockfile": "^4.1.2",
24
+ "ws": "^8.18.3"
25
+ },
26
+ devDependencies: {
27
+ "@types/node": "^22.10.2",
28
+ "@types/proper-lockfile": "^4.1.4",
29
+ "@types/ws": "^8.5.12",
30
+ "tsdown": "^0.17.2",
31
+ "typescript": "^5.9.3",
32
+ "zod": "^4.1.12"
33
+ }
34
+ };
35
+
36
+ //#endregion
37
+ export { package_default as default };
38
+ //# sourceMappingURL=package.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package.mjs","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"@tempad-dev/mcp\",\n \"description\": \"MCP server for TemPad Dev.\",\n \"version\": \"0.3.5\",\n \"type\": \"module\",\n \"main\": \"dist/cli.js\",\n \"bin\": \"dist/cli.js\",\n \"files\": [\n \"dist/**/*\",\n \"README.md\"\n ],\n \"scripts\": {\n \"build\": \"tsdown && cp src/instructions.md dist/instructions.md\",\n \"typecheck\": \"tsc -p tsconfig.json --noEmit\",\n \"lint\": \"eslint . --ext .ts,.mts,.cts,.js,.mjs,.cjs\",\n \"format\": \"prettier --write \\\"**/*.{js,ts,mjs,cjs,cts,mts,json,md}\\\"\",\n \"prepublishOnly\": \"pnpm run build\"\n },\n \"dependencies\": {\n \"@tempad-dev/mcp-shared\": \"workspace:^0.1.0\",\n \"@modelcontextprotocol/sdk\": \"^1.24.0\",\n \"nanoid\": \"^5.1.6\",\n \"pino\": \"^9.14.0\",\n \"pino-pretty\": \"^11.2.2\",\n \"proper-lockfile\": \"^4.1.2\",\n \"ws\": \"^8.18.3\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.10.2\",\n \"@types/proper-lockfile\": \"^4.1.4\",\n \"@types/ws\": \"^8.5.12\",\n \"tsdown\": \"^0.17.2\",\n \"typescript\": \"^5.9.3\",\n \"zod\": \"^4.1.12\"\n }\n}\n"],"mappings":";sBAAA;OACU;cACO;UACJ;OACH;OACA;MACD;QACE,CACP,aACA,YACD;UACU;EACT,SAAS;EACT,aAAa;EACb,QAAQ;EACR,UAAU;EACV,kBAAkB;EACnB;eACe;EACd,0BAA0B;EAC1B,6BAA6B;EAC7B,UAAU;EACV,QAAQ;EACR,eAAe;EACf,mBAAmB;EACnB,MAAM;EACP;kBACkB;EACjB,eAAe;EACf,0BAA0B;EAC1B,aAAa;EACb,UAAU;EACV,cAAc;EACd,OAAO;EACR;CACF"}
@@ -0,0 +1,68 @@
1
+ import { log } from "./shared.mjs";
2
+ import { nanoid } from "nanoid";
3
+
4
+ //#region src/request.ts
5
+ const pendingCalls = /* @__PURE__ */ new Map();
6
+ function register(extensionId, timeout) {
7
+ const requestId = nanoid();
8
+ return {
9
+ promise: new Promise((resolve$1, reject$1) => {
10
+ const timer = setTimeout(() => {
11
+ pendingCalls.delete(requestId);
12
+ reject$1(/* @__PURE__ */ new Error(`Extension did not respond within ${timeout / 1e3}s.`));
13
+ }, timeout);
14
+ pendingCalls.set(requestId, {
15
+ resolve: resolve$1,
16
+ reject: reject$1,
17
+ timer,
18
+ extensionId
19
+ });
20
+ }),
21
+ requestId
22
+ };
23
+ }
24
+ function resolve(requestId, payload) {
25
+ const call = pendingCalls.get(requestId);
26
+ if (call) {
27
+ const { timer, resolve: finish } = call;
28
+ clearTimeout(timer);
29
+ finish(payload);
30
+ pendingCalls.delete(requestId);
31
+ } else log.warn({ reqId: requestId }, "Received result for unknown/timed-out call.");
32
+ }
33
+ function reject(requestId, error) {
34
+ const call = pendingCalls.get(requestId);
35
+ if (call) {
36
+ const { timer, reject: fail } = call;
37
+ clearTimeout(timer);
38
+ fail(error);
39
+ pendingCalls.delete(requestId);
40
+ } else log.warn({ reqId: requestId }, "Received error for unknown/timed-out call.");
41
+ }
42
+ function cleanupForExtension(extensionId) {
43
+ for (const [reqId, call] of pendingCalls.entries()) {
44
+ const { timer, reject: fail, extensionId: extId } = call;
45
+ if (extId === extensionId) {
46
+ clearTimeout(timer);
47
+ fail(/* @__PURE__ */ new Error("Extension disconnected before providing a result."));
48
+ pendingCalls.delete(reqId);
49
+ log.warn({
50
+ reqId,
51
+ extId: extensionId
52
+ }, "Rejected pending call from disconnected extension.");
53
+ }
54
+ }
55
+ }
56
+ function cleanupAll() {
57
+ pendingCalls.forEach((call, reqId) => {
58
+ const { timer, reject: fail } = call;
59
+ clearTimeout(timer);
60
+ fail(/* @__PURE__ */ new Error("Hub is shutting down."));
61
+ log.debug({ reqId }, "Rejected pending tool call due to shutdown.");
62
+ });
63
+ pendingCalls.clear();
64
+ }
65
+
66
+ //#endregion
67
+ export { cleanupAll, cleanupForExtension, register, reject, resolve };
68
+ //# sourceMappingURL=request.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.mjs","names":["resolve"],"sources":["../src/request.ts"],"sourcesContent":["import { nanoid } from 'nanoid'\n\nimport type { PendingToolCall } from './types'\n\nimport { log } from './shared'\n\nconst pendingCalls = new Map<string, PendingToolCall>()\n\nexport function register<T>(\n extensionId: string,\n timeout: number\n): { promise: Promise<T>; requestId: string } {\n const requestId = nanoid()\n const promise = new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n pendingCalls.delete(requestId)\n reject(new Error(`Extension did not respond within ${timeout / 1000}s.`))\n }, timeout)\n\n pendingCalls.set(requestId, {\n resolve: resolve as (value: unknown) => void,\n reject,\n timer,\n extensionId\n })\n })\n return { promise, requestId }\n}\n\nexport function resolve(requestId: string, payload: unknown): void {\n const call = pendingCalls.get(requestId)\n if (call) {\n const { timer, resolve: finish } = call\n clearTimeout(timer)\n finish(payload)\n pendingCalls.delete(requestId)\n } else {\n log.warn({ reqId: requestId }, 'Received result for unknown/timed-out call.')\n }\n}\n\nexport function reject(requestId: string, error: Error): void {\n const call = pendingCalls.get(requestId)\n if (call) {\n const { timer, reject: fail } = call\n clearTimeout(timer)\n fail(error)\n pendingCalls.delete(requestId)\n } else {\n log.warn({ reqId: requestId }, 'Received error for unknown/timed-out call.')\n }\n}\n\nexport function cleanupForExtension(extensionId: string): void {\n for (const [reqId, call] of pendingCalls.entries()) {\n const { timer, reject: fail, extensionId: extId } = call\n if (extId === extensionId) {\n clearTimeout(timer)\n fail(new Error('Extension disconnected before providing a result.'))\n pendingCalls.delete(reqId)\n log.warn({ reqId, extId: extensionId }, 'Rejected pending call from disconnected extension.')\n }\n }\n}\n\nexport function cleanupAll(): void {\n pendingCalls.forEach((call, reqId) => {\n const { timer, reject: fail } = call\n clearTimeout(timer)\n fail(new Error('Hub is shutting down.'))\n log.debug({ reqId }, 'Rejected pending tool call due to shutdown.')\n })\n pendingCalls.clear()\n}\n"],"mappings":";;;;AAMA,MAAM,+BAAe,IAAI,KAA8B;AAEvD,SAAgB,SACd,aACA,SAC4C;CAC5C,MAAM,YAAY,QAAQ;AAc1B,QAAO;EAAE,SAbO,IAAI,SAAY,WAAS,aAAW;GAClD,MAAM,QAAQ,iBAAiB;AAC7B,iBAAa,OAAO,UAAU;AAC9B,6BAAO,IAAI,MAAM,oCAAoC,UAAU,IAAK,IAAI,CAAC;MACxE,QAAQ;AAEX,gBAAa,IAAI,WAAW;IAC1B,SAASA;IACT;IACA;IACA;IACD,CAAC;IACF;EACgB;EAAW;;AAG/B,SAAgB,QAAQ,WAAmB,SAAwB;CACjE,MAAM,OAAO,aAAa,IAAI,UAAU;AACxC,KAAI,MAAM;EACR,MAAM,EAAE,OAAO,SAAS,WAAW;AACnC,eAAa,MAAM;AACnB,SAAO,QAAQ;AACf,eAAa,OAAO,UAAU;OAE9B,KAAI,KAAK,EAAE,OAAO,WAAW,EAAE,8CAA8C;;AAIjF,SAAgB,OAAO,WAAmB,OAAoB;CAC5D,MAAM,OAAO,aAAa,IAAI,UAAU;AACxC,KAAI,MAAM;EACR,MAAM,EAAE,OAAO,QAAQ,SAAS;AAChC,eAAa,MAAM;AACnB,OAAK,MAAM;AACX,eAAa,OAAO,UAAU;OAE9B,KAAI,KAAK,EAAE,OAAO,WAAW,EAAE,6CAA6C;;AAIhF,SAAgB,oBAAoB,aAA2B;AAC7D,MAAK,MAAM,CAAC,OAAO,SAAS,aAAa,SAAS,EAAE;EAClD,MAAM,EAAE,OAAO,QAAQ,MAAM,aAAa,UAAU;AACpD,MAAI,UAAU,aAAa;AACzB,gBAAa,MAAM;AACnB,wBAAK,IAAI,MAAM,oDAAoD,CAAC;AACpE,gBAAa,OAAO,MAAM;AAC1B,OAAI,KAAK;IAAE;IAAO,OAAO;IAAa,EAAE,qDAAqD;;;;AAKnG,SAAgB,aAAmB;AACjC,cAAa,SAAS,MAAM,UAAU;EACpC,MAAM,EAAE,OAAO,QAAQ,SAAS;AAChC,eAAa,MAAM;AACnB,uBAAK,IAAI,MAAM,wBAAwB,CAAC;AACxC,MAAI,MAAM,EAAE,OAAO,EAAE,8CAA8C;GACnE;AACF,cAAa,OAAO"}