@tempad-dev/mcp 0.3.3 → 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/asset-http-server.mjs +239 -0
- package/dist/asset-http-server.mjs.map +1 -0
- package/dist/asset-store.mjs +167 -0
- package/dist/asset-store.mjs.map +1 -0
- package/dist/cli.mjs +147 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/config.mjs +29 -0
- package/dist/config.mjs.map +1 -0
- package/dist/hub.mjs +464 -0
- package/dist/hub.mjs.map +1 -0
- package/dist/instructions.md +11 -0
- package/dist/package.mjs +38 -0
- package/dist/package.mjs.map +1 -0
- package/dist/request.mjs +68 -0
- package/dist/request.mjs.map +1 -0
- package/dist/shared.mjs +57 -0
- package/dist/shared.mjs.map +1 -0
- package/dist/tools.mjs +146 -0
- package/dist/tools.mjs.map +1 -0
- package/package.json +15 -4
- package/dist/cli.js +0 -236
- package/dist/cli.js.map +0 -7
- package/dist/hub.js +0 -1326
- package/dist/hub.js.map +0 -7
package/dist/shared.mjs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import package_default from "./package.mjs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { closeSync, mkdirSync, openSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import pino from "pino";
|
|
6
|
+
|
|
7
|
+
//#region src/shared.ts
|
|
8
|
+
function ensureDir(dirPath) {
|
|
9
|
+
mkdirSync(dirPath, {
|
|
10
|
+
recursive: true,
|
|
11
|
+
mode: 448
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
const pkg = package_default;
|
|
15
|
+
const PACKAGE_VERSION = typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
16
|
+
function resolveRuntimeDir() {
|
|
17
|
+
if (process.env.TEMPAD_MCP_RUNTIME_DIR) return process.env.TEMPAD_MCP_RUNTIME_DIR;
|
|
18
|
+
return join(tmpdir(), "tempad-dev", "run");
|
|
19
|
+
}
|
|
20
|
+
function resolveLogDir() {
|
|
21
|
+
if (process.env.TEMPAD_MCP_LOG_DIR) return process.env.TEMPAD_MCP_LOG_DIR;
|
|
22
|
+
return join(tmpdir(), "tempad-dev", "log");
|
|
23
|
+
}
|
|
24
|
+
function resolveAssetDir() {
|
|
25
|
+
if (process.env.TEMPAD_MCP_ASSET_DIR) return process.env.TEMPAD_MCP_ASSET_DIR;
|
|
26
|
+
return join(tmpdir(), "tempad-dev", "assets");
|
|
27
|
+
}
|
|
28
|
+
const RUNTIME_DIR = resolveRuntimeDir();
|
|
29
|
+
const LOG_DIR = resolveLogDir();
|
|
30
|
+
const ASSET_DIR = resolveAssetDir();
|
|
31
|
+
ensureDir(RUNTIME_DIR);
|
|
32
|
+
ensureDir(LOG_DIR);
|
|
33
|
+
ensureDir(ASSET_DIR);
|
|
34
|
+
function ensureFile(filePath) {
|
|
35
|
+
closeSync(openSync(filePath, "a"));
|
|
36
|
+
}
|
|
37
|
+
const LOCK_PATH = join(RUNTIME_DIR, "mcp.lock");
|
|
38
|
+
ensureFile(LOCK_PATH);
|
|
39
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
|
|
40
|
+
const pid = process.pid;
|
|
41
|
+
const LOG_FILE = join(LOG_DIR, `mcp-${timestamp}-${pid}.log`);
|
|
42
|
+
const prettyTransport = pino.transport({
|
|
43
|
+
target: "pino-pretty",
|
|
44
|
+
options: {
|
|
45
|
+
translateTime: "SYS:HH:MM:ss",
|
|
46
|
+
destination: LOG_FILE
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const log = pino({
|
|
50
|
+
level: process.env.DEBUG ? "debug" : "info",
|
|
51
|
+
msgPrefix: "[tempad-dev/mcp] "
|
|
52
|
+
}, prettyTransport);
|
|
53
|
+
const SOCK_PATH = process.platform === "win32" ? "\\\\.\\pipe\\tempad-mcp" : join(RUNTIME_DIR, "mcp.sock");
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
export { ASSET_DIR, LOCK_PATH, PACKAGE_VERSION, RUNTIME_DIR, SOCK_PATH, ensureDir, ensureFile, log };
|
|
57
|
+
//# sourceMappingURL=shared.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.mjs","names":["packageJson"],"sources":["../src/shared.ts"],"sourcesContent":["import { closeSync, mkdirSync, openSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport pino from 'pino'\n\nimport packageJson from '../package.json' assert { type: 'json' }\n\nexport function ensureDir(dirPath: string): void {\n mkdirSync(dirPath, { recursive: true, mode: 0o700 })\n}\n\nconst pkg = packageJson as { version?: unknown }\nexport const PACKAGE_VERSION = typeof pkg.version === 'string' ? pkg.version : '0.0.0'\n\nfunction resolveRuntimeDir(): string {\n if (process.env.TEMPAD_MCP_RUNTIME_DIR) return process.env.TEMPAD_MCP_RUNTIME_DIR\n return join(tmpdir(), 'tempad-dev', 'run')\n}\n\nfunction resolveLogDir(): string {\n if (process.env.TEMPAD_MCP_LOG_DIR) return process.env.TEMPAD_MCP_LOG_DIR\n return join(tmpdir(), 'tempad-dev', 'log')\n}\n\nfunction resolveAssetDir(): string {\n if (process.env.TEMPAD_MCP_ASSET_DIR) return process.env.TEMPAD_MCP_ASSET_DIR\n return join(tmpdir(), 'tempad-dev', 'assets')\n}\n\nexport const RUNTIME_DIR = resolveRuntimeDir()\nexport const LOG_DIR = resolveLogDir()\nexport const ASSET_DIR = resolveAssetDir()\n\nensureDir(RUNTIME_DIR)\nensureDir(LOG_DIR)\nensureDir(ASSET_DIR)\n\nexport function ensureFile(filePath: string): void {\n const fd = openSync(filePath, 'a')\n closeSync(fd)\n}\n\nexport const LOCK_PATH = join(RUNTIME_DIR, 'mcp.lock')\nensureFile(LOCK_PATH)\n\nconst timestamp = new Date().toISOString().replaceAll(':', '-').replaceAll('.', '-')\nconst pid = process.pid\nconst LOG_FILE = join(LOG_DIR, `mcp-${timestamp}-${pid}.log`)\n\nconst prettyTransport = pino.transport({\n target: 'pino-pretty',\n options: {\n translateTime: 'SYS:HH:MM:ss',\n destination: LOG_FILE\n }\n})\n\nexport const log = pino(\n {\n level: process.env.DEBUG ? 'debug' : 'info',\n msgPrefix: '[tempad-dev/mcp] '\n },\n prettyTransport\n)\n\nexport const SOCK_PATH =\n process.platform === 'win32' ? '\\\\\\\\.\\\\pipe\\\\tempad-mcp' : join(RUNTIME_DIR, 'mcp.sock')\n"],"mappings":";;;;;;;AAOA,SAAgB,UAAU,SAAuB;AAC/C,WAAU,SAAS;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;;AAGtD,MAAM,MAAMA;AACZ,MAAa,kBAAkB,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;AAE/E,SAAS,oBAA4B;AACnC,KAAI,QAAQ,IAAI,uBAAwB,QAAO,QAAQ,IAAI;AAC3D,QAAO,KAAK,QAAQ,EAAE,cAAc,MAAM;;AAG5C,SAAS,gBAAwB;AAC/B,KAAI,QAAQ,IAAI,mBAAoB,QAAO,QAAQ,IAAI;AACvD,QAAO,KAAK,QAAQ,EAAE,cAAc,MAAM;;AAG5C,SAAS,kBAA0B;AACjC,KAAI,QAAQ,IAAI,qBAAsB,QAAO,QAAQ,IAAI;AACzD,QAAO,KAAK,QAAQ,EAAE,cAAc,SAAS;;AAG/C,MAAa,cAAc,mBAAmB;AAC9C,MAAa,UAAU,eAAe;AACtC,MAAa,YAAY,iBAAiB;AAE1C,UAAU,YAAY;AACtB,UAAU,QAAQ;AAClB,UAAU,UAAU;AAEpB,SAAgB,WAAW,UAAwB;AAEjD,WADW,SAAS,UAAU,IAAI,CACrB;;AAGf,MAAa,YAAY,KAAK,aAAa,WAAW;AACtD,WAAW,UAAU;AAErB,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa,CAAC,WAAW,KAAK,IAAI,CAAC,WAAW,KAAK,IAAI;AACpF,MAAM,MAAM,QAAQ;AACpB,MAAM,WAAW,KAAK,SAAS,OAAO,UAAU,GAAG,IAAI,MAAM;AAE7D,MAAM,kBAAkB,KAAK,UAAU;CACrC,QAAQ;CACR,SAAS;EACP,eAAe;EACf,aAAa;EACd;CACF,CAAC;AAEF,MAAa,MAAM,KACjB;CACE,OAAO,QAAQ,IAAI,QAAQ,UAAU;CACrC,WAAW;CACZ,EACD,gBACD;AAED,MAAa,YACX,QAAQ,aAAa,UAAU,4BAA4B,KAAK,aAAa,WAAW"}
|
package/dist/tools.mjs
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { GetAssetsParametersSchema, GetAssetsResultSchema, GetCodeParametersSchema, GetScreenshotParametersSchema, GetStructureParametersSchema, GetTokenDefsParametersSchema } from "@tempad-dev/mcp-shared";
|
|
5
|
+
|
|
6
|
+
//#region src/tools.ts
|
|
7
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const MCP_INSTRUCTIONS = readFileSync(join(HERE, "instructions.md"), "utf8");
|
|
9
|
+
function extTool(definition) {
|
|
10
|
+
return definition;
|
|
11
|
+
}
|
|
12
|
+
function hubTool(definition) {
|
|
13
|
+
return definition;
|
|
14
|
+
}
|
|
15
|
+
const TOOL_DEFS = [
|
|
16
|
+
extTool({
|
|
17
|
+
name: "get_code",
|
|
18
|
+
description: "Get a high-fidelity code snapshot for a nodeId/current selection, including assets/usedTokens and codegen preset/config. Start here, then refactor into your component/styling/file/naming conventions; strip any data-* hints. If no data-hint-auto-layout is present, layout is explicit; if any hint is none/inferred, pair with get_structure/get_screenshot to confirm hierarchy/overlap. Use data-hint-component plus repetition to decide on reusable components. Replace resource URIs with your canonical asset system as needed.",
|
|
19
|
+
parameters: GetCodeParametersSchema,
|
|
20
|
+
target: "extension",
|
|
21
|
+
format: createCodeToolResponse
|
|
22
|
+
}),
|
|
23
|
+
extTool({
|
|
24
|
+
name: "get_token_defs",
|
|
25
|
+
description: "Resolve canonical token names to values (including modes) for tokens referenced by get_code. Use this to map into your design token/theming system, including responsive tokens.",
|
|
26
|
+
parameters: GetTokenDefsParametersSchema,
|
|
27
|
+
target: "extension",
|
|
28
|
+
exposed: false
|
|
29
|
+
}),
|
|
30
|
+
extTool({
|
|
31
|
+
name: "get_screenshot",
|
|
32
|
+
description: "Capture a rendered screenshot for a nodeId/current selection for visual verification. Useful for confirming layering/overlap/masks/shadows/translucency when auto-layout hints are none/inferred.",
|
|
33
|
+
parameters: GetScreenshotParametersSchema,
|
|
34
|
+
target: "extension",
|
|
35
|
+
format: createScreenshotToolResponse
|
|
36
|
+
}),
|
|
37
|
+
extTool({
|
|
38
|
+
name: "get_structure",
|
|
39
|
+
description: "Get a structural + geometry outline for a nodeId/current selection to understand hierarchy and layout intent. Use when auto-layout hints are none/inferred or you need explicit bounds for refactors/component extraction.",
|
|
40
|
+
parameters: GetStructureParametersSchema,
|
|
41
|
+
target: "extension"
|
|
42
|
+
}),
|
|
43
|
+
hubTool({
|
|
44
|
+
name: "get_assets",
|
|
45
|
+
description: "Resolve asset hashes to downloadable URLs/URIs for assets referenced by get_code, preserving vectors exactly. Pull bytes before routing through your asset/icon pipeline.",
|
|
46
|
+
parameters: GetAssetsParametersSchema,
|
|
47
|
+
target: "hub",
|
|
48
|
+
outputSchema: GetAssetsResultSchema,
|
|
49
|
+
exposed: false
|
|
50
|
+
})
|
|
51
|
+
];
|
|
52
|
+
function createToolErrorResponse(toolName, error) {
|
|
53
|
+
return { content: [{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: `Tool "${toolName}" failed: ${error instanceof Error ? error.message || "Unknown error occurred." : typeof error === "string" ? error : "Unknown error occurred."}`
|
|
56
|
+
}] };
|
|
57
|
+
}
|
|
58
|
+
function formatBytes(bytes) {
|
|
59
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
60
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
61
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
62
|
+
}
|
|
63
|
+
function createCodeToolResponse(payload) {
|
|
64
|
+
if (!isCodeResult(payload)) throw new Error("Invalid get_code payload received from extension.");
|
|
65
|
+
const summary = [];
|
|
66
|
+
const codeSize = Buffer.byteLength(payload.code, "utf8");
|
|
67
|
+
summary.push(`Generated \`${payload.lang}\` snippet (${formatBytes(codeSize)}).`);
|
|
68
|
+
if (payload.message) summary.push(payload.message);
|
|
69
|
+
summary.push(payload.assets.length ? `Assets attached: ${payload.assets.length}. Fetch bytes via resources/read using resourceUri.` : "No binary assets were attached to this response.");
|
|
70
|
+
const tokenCount = payload.usedTokens ? Object.keys(payload.usedTokens.tokens ?? {}).length : 0;
|
|
71
|
+
if (tokenCount) summary.push(`Token references included: ${tokenCount}.`);
|
|
72
|
+
summary.push("Read structuredContent for the full code string and asset metadata.");
|
|
73
|
+
const assetLinks = payload.assets.length > 0 ? payload.assets.map((asset) => createAssetResourceLinkBlock(asset)) : [];
|
|
74
|
+
return {
|
|
75
|
+
content: [{
|
|
76
|
+
type: "text",
|
|
77
|
+
text: summary.join("\n")
|
|
78
|
+
}, ...assetLinks],
|
|
79
|
+
structuredContent: payload
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function createScreenshotToolResponse(payload) {
|
|
83
|
+
if (!isScreenshotResult(payload)) throw new Error("Invalid get_screenshot payload received from extension.");
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: describeScreenshot(payload)
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: "text",
|
|
92
|
+
text: ``
|
|
93
|
+
},
|
|
94
|
+
createResourceLinkBlock(payload.asset, payload)
|
|
95
|
+
],
|
|
96
|
+
structuredContent: payload
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function createResourceLinkBlock(asset, result) {
|
|
100
|
+
return {
|
|
101
|
+
type: "resource_link",
|
|
102
|
+
name: "Screenshot",
|
|
103
|
+
uri: asset.resourceUri,
|
|
104
|
+
mimeType: asset.mimeType,
|
|
105
|
+
description: `Screenshot ${result.width}x${result.height} @${result.scale}x - Download: ${asset.url}`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function describeScreenshot(result) {
|
|
109
|
+
return `Screenshot ${result.width}x${result.height} @${result.scale}x (${formatBytes(result.bytes)})`;
|
|
110
|
+
}
|
|
111
|
+
function isScreenshotResult(payload) {
|
|
112
|
+
if (typeof payload !== "object" || !payload) return false;
|
|
113
|
+
const candidate = payload;
|
|
114
|
+
return typeof candidate.asset === "object" && candidate.asset !== null && typeof candidate.width === "number" && typeof candidate.height === "number" && typeof candidate.scale === "number" && typeof candidate.bytes === "number" && typeof candidate.format === "string";
|
|
115
|
+
}
|
|
116
|
+
function isCodeResult(payload) {
|
|
117
|
+
if (typeof payload !== "object" || !payload) return false;
|
|
118
|
+
const candidate = payload;
|
|
119
|
+
return typeof candidate.code === "string" && typeof candidate.lang === "string" && Array.isArray(candidate.assets);
|
|
120
|
+
}
|
|
121
|
+
function createAssetResourceLinkBlock(asset) {
|
|
122
|
+
return {
|
|
123
|
+
type: "resource_link",
|
|
124
|
+
name: formatAssetResourceName(asset.hash),
|
|
125
|
+
uri: asset.resourceUri,
|
|
126
|
+
mimeType: asset.mimeType,
|
|
127
|
+
description: `${describeAsset(asset)} - Download: ${asset.url}`
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function describeAsset(asset) {
|
|
131
|
+
return `${asset.mimeType} (${formatBytes(asset.size)})`;
|
|
132
|
+
}
|
|
133
|
+
function formatAssetResourceName(hash) {
|
|
134
|
+
return `asset:${hash.slice(0, 8)}`;
|
|
135
|
+
}
|
|
136
|
+
function coercePayloadToToolResponse(payload) {
|
|
137
|
+
if (payload && typeof payload === "object" && Array.isArray(payload.content)) return payload;
|
|
138
|
+
return { content: [{
|
|
139
|
+
type: "text",
|
|
140
|
+
text: typeof payload === "string" ? payload : JSON.stringify(payload, null, 2)
|
|
141
|
+
}] };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
export { MCP_INSTRUCTIONS, TOOL_DEFS, coercePayloadToToolResponse, createToolErrorResponse };
|
|
146
|
+
//# sourceMappingURL=tools.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.mjs","names":["summary: string[]"],"sources":["../src/tools.ts"],"sourcesContent":["import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport type {\n AssetDescriptor,\n GetScreenshotResult,\n ToolName,\n ToolResultMap,\n ToolSchema\n} from '@tempad-dev/mcp-shared'\nimport type { ZodType } from 'zod'\n\nimport {\n GetAssetsParametersSchema,\n GetAssetsResultSchema,\n GetCodeParametersSchema,\n GetScreenshotParametersSchema,\n GetStructureParametersSchema,\n GetTokenDefsParametersSchema\n} from '@tempad-dev/mcp-shared'\nimport { readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nexport type {\n AssetDescriptor,\n GetAssetsParametersInput,\n GetAssetsResult,\n GetCodeParametersInput,\n GetCodeResult,\n GetScreenshotParametersInput,\n GetScreenshotResult,\n GetStructureParametersInput,\n GetStructureResult,\n GetTokenDefsParametersInput,\n GetTokenDefsResult,\n TokenEntry,\n ToolName,\n ToolResultMap,\n ToolSchema\n} from '@tempad-dev/mcp-shared'\n\nconst HERE = dirname(fileURLToPath(import.meta.url))\nexport const MCP_INSTRUCTIONS = readFileSync(join(HERE, 'instructions.md'), 'utf8')\n\ntype BaseToolMetadata<Name extends ToolName, Schema extends ZodType> = ToolSchema<Name> & {\n parameters: Schema\n format?: (payload: ToolResultMap[Name]) => CallToolResult\n}\n\ntype ExtensionToolMetadata<Name extends ToolName, Schema extends ZodType> = BaseToolMetadata<\n Name,\n Schema\n> & {\n target: 'extension'\n}\n\ntype HubToolMetadata<Name extends ToolName, Schema extends ZodType> = BaseToolMetadata<\n Name,\n Schema\n> & {\n target: 'hub'\n outputSchema?: ZodType\n}\n\nfunction extTool<Name extends ToolName, Schema extends ZodType>(\n definition: ExtensionToolMetadata<Name, Schema>\n): ExtensionToolMetadata<Name, Schema> {\n return definition\n}\n\nfunction hubTool<Name extends ToolName, Schema extends ZodType>(\n definition: HubToolMetadata<Name, Schema>\n): HubToolMetadata<Name, Schema> {\n return definition\n}\n\nexport const TOOL_DEFS = [\n extTool({\n name: 'get_code',\n description:\n 'Get a high-fidelity code snapshot for a nodeId/current selection, including assets/usedTokens and codegen preset/config. Start here, then refactor into your component/styling/file/naming conventions; strip any data-* hints. If no data-hint-auto-layout is present, layout is explicit; if any hint is none/inferred, pair with get_structure/get_screenshot to confirm hierarchy/overlap. Use data-hint-component plus repetition to decide on reusable components. Replace resource URIs with your canonical asset system as needed.',\n parameters: GetCodeParametersSchema,\n target: 'extension',\n format: createCodeToolResponse\n }),\n extTool({\n name: 'get_token_defs',\n description:\n 'Resolve canonical token names to values (including modes) for tokens referenced by get_code. Use this to map into your design token/theming system, including responsive tokens.',\n parameters: GetTokenDefsParametersSchema,\n target: 'extension',\n exposed: false\n }),\n extTool({\n name: 'get_screenshot',\n description:\n 'Capture a rendered screenshot for a nodeId/current selection for visual verification. Useful for confirming layering/overlap/masks/shadows/translucency when auto-layout hints are none/inferred.',\n parameters: GetScreenshotParametersSchema,\n target: 'extension',\n format: createScreenshotToolResponse\n }),\n extTool({\n name: 'get_structure',\n description:\n 'Get a structural + geometry outline for a nodeId/current selection to understand hierarchy and layout intent. Use when auto-layout hints are none/inferred or you need explicit bounds for refactors/component extraction.',\n parameters: GetStructureParametersSchema,\n target: 'extension'\n }),\n hubTool({\n name: 'get_assets',\n description:\n 'Resolve asset hashes to downloadable URLs/URIs for assets referenced by get_code, preserving vectors exactly. Pull bytes before routing through your asset/icon pipeline.',\n parameters: GetAssetsParametersSchema,\n target: 'hub',\n outputSchema: GetAssetsResultSchema,\n exposed: false\n })\n] as const\n\nfunction createToolErrorResponse(toolName: string, error: unknown): CallToolResult {\n const message =\n error instanceof Error\n ? error.message || 'Unknown error occurred.'\n : typeof error === 'string'\n ? error\n : 'Unknown error occurred.'\n return {\n content: [\n {\n type: 'text' as const,\n text: `Tool \"${toolName}\" failed: ${message}`\n }\n ]\n }\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\nexport function createCodeToolResponse(payload: ToolResultMap['get_code']): CallToolResult {\n if (!isCodeResult(payload)) {\n throw new Error('Invalid get_code payload received from extension.')\n }\n\n const summary: string[] = []\n const codeSize = Buffer.byteLength(payload.code, 'utf8')\n summary.push(`Generated \\`${payload.lang}\\` snippet (${formatBytes(codeSize)}).`)\n if (payload.message) {\n summary.push(payload.message)\n }\n summary.push(\n payload.assets.length\n ? `Assets attached: ${payload.assets.length}. Fetch bytes via resources/read using resourceUri.`\n : 'No binary assets were attached to this response.'\n )\n const tokenCount = payload.usedTokens ? Object.keys(payload.usedTokens.tokens ?? {}).length : 0\n if (tokenCount) {\n summary.push(`Token references included: ${tokenCount}.`)\n }\n summary.push('Read structuredContent for the full code string and asset metadata.')\n\n const assetLinks =\n payload.assets.length > 0\n ? payload.assets.map((asset) => createAssetResourceLinkBlock(asset))\n : []\n\n return {\n content: [\n {\n type: 'text' as const,\n text: summary.join('\\n')\n },\n ...assetLinks\n ],\n structuredContent: payload\n }\n}\n\nexport function createScreenshotToolResponse(\n payload: ToolResultMap['get_screenshot']\n): CallToolResult {\n if (!isScreenshotResult(payload)) {\n throw new Error('Invalid get_screenshot payload received from extension.')\n }\n\n const descriptionBlock = {\n type: 'text' as const,\n text: describeScreenshot(payload)\n }\n\n return {\n content: [\n descriptionBlock,\n {\n type: 'text' as const,\n text: ``\n },\n createResourceLinkBlock(payload.asset, payload)\n ],\n structuredContent: payload\n }\n}\n\nfunction createResourceLinkBlock(asset: AssetDescriptor, result: GetScreenshotResult) {\n return {\n type: 'resource_link' as const,\n name: 'Screenshot',\n uri: asset.resourceUri,\n mimeType: asset.mimeType,\n description: `Screenshot ${result.width}x${result.height} @${result.scale}x - Download: ${asset.url}`\n }\n}\n\nfunction describeScreenshot(result: GetScreenshotResult): string {\n return `Screenshot ${result.width}x${result.height} @${result.scale}x (${formatBytes(result.bytes)})`\n}\n\nfunction isScreenshotResult(payload: unknown): payload is GetScreenshotResult {\n if (typeof payload !== 'object' || !payload) return false\n const candidate = payload as Partial<GetScreenshotResult & Record<string, unknown>>\n return (\n typeof candidate.asset === 'object' &&\n candidate.asset !== null &&\n typeof candidate.width === 'number' &&\n typeof candidate.height === 'number' &&\n typeof candidate.scale === 'number' &&\n typeof candidate.bytes === 'number' &&\n typeof candidate.format === 'string'\n )\n}\n\nfunction isCodeResult(payload: unknown): payload is ToolResultMap['get_code'] {\n if (typeof payload !== 'object' || !payload) return false\n const candidate = payload as Partial<ToolResultMap['get_code'] & Record<string, unknown>>\n return (\n typeof candidate.code === 'string' &&\n typeof candidate.lang === 'string' &&\n Array.isArray(candidate.assets)\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 formatAssetResourceName(hash: string): string {\n return `asset:${hash.slice(0, 8)}`\n}\n\nexport function coercePayloadToToolResponse(payload: unknown): CallToolResult {\n if (\n payload &&\n typeof payload === 'object' &&\n Array.isArray((payload as CallToolResult).content)\n ) {\n return payload as CallToolResult\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2)\n }\n ]\n }\n}\n\nexport { createToolErrorResponse }\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tempad-dev/mcp",
|
|
3
3
|
"description": "MCP server for TemPad Dev.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli.js",
|
|
7
7
|
"bin": "dist/cli.js",
|
|
@@ -10,15 +10,26 @@
|
|
|
10
10
|
"README.md"
|
|
11
11
|
],
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@
|
|
13
|
+
"@tempad-dev/mcp-shared": "^0.1.0",
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.24.0",
|
|
14
15
|
"nanoid": "^5.1.6",
|
|
15
16
|
"pino": "^9.14.0",
|
|
16
17
|
"pino-pretty": "^11.2.2",
|
|
17
18
|
"proper-lockfile": "^4.1.2",
|
|
18
|
-
"ws": "^8.18.3"
|
|
19
|
+
"ws": "^8.18.3"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.10.2",
|
|
23
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
24
|
+
"@types/ws": "^8.5.12",
|
|
25
|
+
"tsdown": "^0.17.2",
|
|
26
|
+
"typescript": "^5.9.3",
|
|
19
27
|
"zod": "^4.1.12"
|
|
20
28
|
},
|
|
21
29
|
"scripts": {
|
|
22
|
-
"build": "
|
|
30
|
+
"build": "tsdown && cp src/instructions.md dist/instructions.md",
|
|
31
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
32
|
+
"lint": "eslint . --ext .ts,.mts,.cts,.js,.mjs,.cjs",
|
|
33
|
+
"format": "prettier --write \"**/*.{js,ts,mjs,cjs,cts,mts,json,md}\""
|
|
23
34
|
}
|
|
24
35
|
}
|
package/dist/cli.js
DELETED
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/cli.ts
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
5
|
-
import { connect } from "node:net";
|
|
6
|
-
import { join as join2 } from "node:path";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
-
import lockfile from "proper-lockfile";
|
|
9
|
-
|
|
10
|
-
// src/shared.ts
|
|
11
|
-
import { closeSync, mkdirSync, openSync } from "node:fs";
|
|
12
|
-
import { tmpdir } from "node:os";
|
|
13
|
-
import { join } from "node:path";
|
|
14
|
-
import pino from "pino";
|
|
15
|
-
|
|
16
|
-
// package.json
|
|
17
|
-
var package_default = {
|
|
18
|
-
name: "@tempad-dev/mcp",
|
|
19
|
-
description: "MCP server for TemPad Dev.",
|
|
20
|
-
version: "0.3.3",
|
|
21
|
-
type: "module",
|
|
22
|
-
main: "dist/cli.js",
|
|
23
|
-
bin: "dist/cli.js",
|
|
24
|
-
files: [
|
|
25
|
-
"dist/**/*",
|
|
26
|
-
"README.md"
|
|
27
|
-
],
|
|
28
|
-
scripts: {
|
|
29
|
-
build: "tsc -p ./tsconfig.json --noEmit && node ./scripts/build.mjs",
|
|
30
|
-
prepublishOnly: "npm run build"
|
|
31
|
-
},
|
|
32
|
-
dependencies: {
|
|
33
|
-
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
34
|
-
nanoid: "^5.1.6",
|
|
35
|
-
pino: "^9.14.0",
|
|
36
|
-
"pino-pretty": "^11.2.2",
|
|
37
|
-
"proper-lockfile": "^4.1.2",
|
|
38
|
-
ws: "^8.18.3",
|
|
39
|
-
zod: "^4.1.12"
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// src/shared.ts
|
|
44
|
-
function ensureDir(dirPath) {
|
|
45
|
-
mkdirSync(dirPath, { recursive: true, mode: 448 });
|
|
46
|
-
}
|
|
47
|
-
var pkg = package_default;
|
|
48
|
-
var PACKAGE_VERSION = typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
49
|
-
function resolveRuntimeDir() {
|
|
50
|
-
if (process.env.TEMPAD_MCP_RUNTIME_DIR) return process.env.TEMPAD_MCP_RUNTIME_DIR;
|
|
51
|
-
return join(tmpdir(), "tempad-dev", "run");
|
|
52
|
-
}
|
|
53
|
-
function resolveLogDir() {
|
|
54
|
-
if (process.env.TEMPAD_MCP_LOG_DIR) return process.env.TEMPAD_MCP_LOG_DIR;
|
|
55
|
-
return join(tmpdir(), "tempad-dev", "log");
|
|
56
|
-
}
|
|
57
|
-
function resolveAssetDir() {
|
|
58
|
-
if (process.env.TEMPAD_MCP_ASSET_DIR) return process.env.TEMPAD_MCP_ASSET_DIR;
|
|
59
|
-
return join(tmpdir(), "tempad-dev", "assets");
|
|
60
|
-
}
|
|
61
|
-
var RUNTIME_DIR = resolveRuntimeDir();
|
|
62
|
-
var LOG_DIR = resolveLogDir();
|
|
63
|
-
var ASSET_DIR = resolveAssetDir();
|
|
64
|
-
ensureDir(RUNTIME_DIR);
|
|
65
|
-
ensureDir(LOG_DIR);
|
|
66
|
-
ensureDir(ASSET_DIR);
|
|
67
|
-
function ensureFile(filePath) {
|
|
68
|
-
const fd = openSync(filePath, "a");
|
|
69
|
-
closeSync(fd);
|
|
70
|
-
}
|
|
71
|
-
var LOCK_PATH = join(RUNTIME_DIR, "mcp.lock");
|
|
72
|
-
ensureFile(LOCK_PATH);
|
|
73
|
-
var timestamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
|
|
74
|
-
var pid = process.pid;
|
|
75
|
-
var LOG_FILE = join(LOG_DIR, `mcp-${timestamp}-${pid}.log`);
|
|
76
|
-
var prettyTransport = pino.transport({
|
|
77
|
-
target: "pino-pretty",
|
|
78
|
-
options: {
|
|
79
|
-
translateTime: "SYS:HH:MM:ss",
|
|
80
|
-
destination: LOG_FILE
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
var log = pino(
|
|
84
|
-
{
|
|
85
|
-
level: process.env.DEBUG ? "debug" : "info",
|
|
86
|
-
msgPrefix: "[tempad-dev/mcp] "
|
|
87
|
-
},
|
|
88
|
-
prettyTransport
|
|
89
|
-
);
|
|
90
|
-
var SOCK_PATH = process.platform === "win32" ? "\\\\.\\pipe\\tempad-mcp" : join(RUNTIME_DIR, "mcp.sock");
|
|
91
|
-
|
|
92
|
-
// src/cli.ts
|
|
93
|
-
var activeSocket = null;
|
|
94
|
-
var shuttingDown = false;
|
|
95
|
-
function closeActiveSocket() {
|
|
96
|
-
if (!activeSocket) return;
|
|
97
|
-
try {
|
|
98
|
-
activeSocket.end();
|
|
99
|
-
} catch {
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
activeSocket.destroy();
|
|
103
|
-
} catch {
|
|
104
|
-
}
|
|
105
|
-
activeSocket = null;
|
|
106
|
-
}
|
|
107
|
-
function shutdownCli(reason) {
|
|
108
|
-
if (shuttingDown) return;
|
|
109
|
-
shuttingDown = true;
|
|
110
|
-
log.info(`${reason} Shutting down CLI.`);
|
|
111
|
-
closeActiveSocket();
|
|
112
|
-
process.exit(0);
|
|
113
|
-
}
|
|
114
|
-
process.on("SIGINT", () => shutdownCli("SIGINT received."));
|
|
115
|
-
process.on("SIGTERM", () => shutdownCli("SIGTERM received."));
|
|
116
|
-
var HUB_STARTUP_TIMEOUT = 5e3;
|
|
117
|
-
var CONNECT_RETRY_DELAY = 200;
|
|
118
|
-
var FAILED_RESTART_DELAY = 5e3;
|
|
119
|
-
var HERE = fileURLToPath(new URL(".", import.meta.url));
|
|
120
|
-
var HUB_ENTRY = join2(HERE, "hub.js");
|
|
121
|
-
ensureDir(RUNTIME_DIR);
|
|
122
|
-
function bridge(socket) {
|
|
123
|
-
return new Promise((resolve) => {
|
|
124
|
-
log.info("Bridge established with Hub. Forwarding I/O.");
|
|
125
|
-
activeSocket = socket;
|
|
126
|
-
const onStdinEnd = () => {
|
|
127
|
-
shutdownCli("Consumer stream ended.");
|
|
128
|
-
};
|
|
129
|
-
process.stdin.once("end", onStdinEnd);
|
|
130
|
-
const onSocketClose = () => {
|
|
131
|
-
log.warn("Connection to Hub lost. Attempting to reconnect...");
|
|
132
|
-
activeSocket = null;
|
|
133
|
-
process.stdin.removeListener("end", onStdinEnd);
|
|
134
|
-
process.stdin.unpipe(socket);
|
|
135
|
-
socket.unpipe(process.stdout);
|
|
136
|
-
socket.removeAllListeners();
|
|
137
|
-
resolve();
|
|
138
|
-
};
|
|
139
|
-
socket.once("close", onSocketClose);
|
|
140
|
-
socket.on("error", (err) => log.warn({ err }, "Socket error occurred."));
|
|
141
|
-
process.stdin.pipe(socket, { end: false }).pipe(process.stdout);
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
function connectHub() {
|
|
145
|
-
return new Promise((resolve, reject) => {
|
|
146
|
-
const socket = connect(SOCK_PATH);
|
|
147
|
-
socket.on("connect", () => {
|
|
148
|
-
socket.removeAllListeners("error");
|
|
149
|
-
resolve(socket);
|
|
150
|
-
});
|
|
151
|
-
socket.on("error", reject);
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
async function connectWithRetry(timeout) {
|
|
155
|
-
const startTime = Date.now();
|
|
156
|
-
let delay = CONNECT_RETRY_DELAY;
|
|
157
|
-
while (Date.now() - startTime < timeout) {
|
|
158
|
-
try {
|
|
159
|
-
return await connectHub();
|
|
160
|
-
} catch (err) {
|
|
161
|
-
if (err && typeof err === "object" && "code" in err && (err.code === "ENOENT" || err.code === "ECONNREFUSED")) {
|
|
162
|
-
const remainingTime = timeout - (Date.now() - startTime);
|
|
163
|
-
const waitTime = Math.min(delay, remainingTime);
|
|
164
|
-
if (waitTime <= 0) break;
|
|
165
|
-
await new Promise((r) => setTimeout(r, waitTime));
|
|
166
|
-
delay = Math.min(delay * 1.5, 1e3);
|
|
167
|
-
} else {
|
|
168
|
-
throw err;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
throw new Error(`Failed to connect to Hub within ${timeout}ms.`);
|
|
173
|
-
}
|
|
174
|
-
function startHub() {
|
|
175
|
-
log.info("Spawning new Hub process...");
|
|
176
|
-
return spawn(process.execPath, [HUB_ENTRY], {
|
|
177
|
-
detached: true,
|
|
178
|
-
stdio: "ignore"
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
async function tryBecomeLeaderAndStartHub() {
|
|
182
|
-
let releaseLock = null;
|
|
183
|
-
try {
|
|
184
|
-
releaseLock = await lockfile.lock(LOCK_PATH, {
|
|
185
|
-
retries: { retries: 5, factor: 1.2, minTimeout: 50 },
|
|
186
|
-
stale: 15e3
|
|
187
|
-
});
|
|
188
|
-
} catch {
|
|
189
|
-
log.info("Another process is starting the Hub. Waiting...");
|
|
190
|
-
return connectWithRetry(HUB_STARTUP_TIMEOUT);
|
|
191
|
-
}
|
|
192
|
-
log.info("Acquired lock. Starting Hub as the leader...");
|
|
193
|
-
let child = null;
|
|
194
|
-
try {
|
|
195
|
-
try {
|
|
196
|
-
return await connectHub();
|
|
197
|
-
} catch {
|
|
198
|
-
log.info("Hub not running. Proceeding to start it...");
|
|
199
|
-
}
|
|
200
|
-
child = startHub();
|
|
201
|
-
child.on("error", (err) => log.error({ err }, "Hub child process error."));
|
|
202
|
-
const socket = await connectWithRetry(HUB_STARTUP_TIMEOUT);
|
|
203
|
-
child.unref();
|
|
204
|
-
return socket;
|
|
205
|
-
} catch (err) {
|
|
206
|
-
log.error({ err }, "Failed to start or connect to the Hub.");
|
|
207
|
-
if (child && !child.killed) {
|
|
208
|
-
log.warn(`Killing stale Hub process (PID: ${child.pid})...`);
|
|
209
|
-
child.kill("SIGTERM");
|
|
210
|
-
}
|
|
211
|
-
throw err;
|
|
212
|
-
} finally {
|
|
213
|
-
if (releaseLock) await releaseLock();
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
async function main() {
|
|
217
|
-
log.info({ version: PACKAGE_VERSION }, "TemPad MCP Client starting...");
|
|
218
|
-
while (true) {
|
|
219
|
-
try {
|
|
220
|
-
const socket = await connectHub().catch(() => {
|
|
221
|
-
log.info("Hub not running. Initiating startup sequence...");
|
|
222
|
-
return tryBecomeLeaderAndStartHub();
|
|
223
|
-
});
|
|
224
|
-
await bridge(socket);
|
|
225
|
-
log.info("Bridge disconnected. Restarting connection process...");
|
|
226
|
-
} catch (err) {
|
|
227
|
-
log.error(
|
|
228
|
-
{ err },
|
|
229
|
-
`Connection attempt failed. Retrying in ${FAILED_RESTART_DELAY / 1e3}s...`
|
|
230
|
-
);
|
|
231
|
-
await new Promise((r) => setTimeout(r, FAILED_RESTART_DELAY));
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
main();
|
|
236
|
-
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/cli.ts", "../src/shared.ts", "../package.json"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n\nimport type { ChildProcess } from 'node:child_process'\nimport type { Socket } from 'node:net'\n\nimport { spawn } from 'node:child_process'\nimport { connect } from 'node:net'\nimport { join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport lockfile from 'proper-lockfile'\n\nimport { PACKAGE_VERSION, log, LOCK_PATH, RUNTIME_DIR, SOCK_PATH, ensureDir } from './shared'\n\nlet activeSocket: Socket | null = null\nlet shuttingDown = false\n\nfunction closeActiveSocket() {\n if (!activeSocket) return\n try {\n activeSocket.end()\n } catch {\n // ignore\n }\n try {\n activeSocket.destroy()\n } catch {\n // ignore\n }\n activeSocket = null\n}\n\nfunction shutdownCli(reason: string) {\n if (shuttingDown) return\n shuttingDown = true\n log.info(`${reason} Shutting down CLI.`)\n closeActiveSocket()\n process.exit(0)\n}\n\nprocess.on('SIGINT', () => shutdownCli('SIGINT received.'))\nprocess.on('SIGTERM', () => shutdownCli('SIGTERM received.'))\n\nconst HUB_STARTUP_TIMEOUT = 5000\nconst CONNECT_RETRY_DELAY = 200\nconst FAILED_RESTART_DELAY = 5000\nconst HERE = fileURLToPath(new URL('.', import.meta.url))\nconst HUB_ENTRY = join(HERE, 'hub.js')\n\nensureDir(RUNTIME_DIR)\n\nfunction bridge(socket: Socket): Promise<void> {\n return new Promise((resolve) => {\n log.info('Bridge established with Hub. Forwarding I/O.')\n activeSocket = socket\n\n const onStdinEnd = () => {\n shutdownCli('Consumer stream ended.')\n }\n process.stdin.once('end', onStdinEnd)\n\n const onSocketClose = () => {\n log.warn('Connection to Hub lost. Attempting to reconnect...')\n activeSocket = null\n process.stdin.removeListener('end', onStdinEnd)\n process.stdin.unpipe(socket)\n socket.unpipe(process.stdout)\n socket.removeAllListeners()\n resolve()\n }\n socket.once('close', onSocketClose)\n socket.on('error', (err) => log.warn({ err }, 'Socket error occurred.'))\n\n // The `{ end: false }` option prevents stdin from closing the socket.\n process.stdin.pipe(socket, { end: false }).pipe(process.stdout)\n })\n}\n\nfunction connectHub(): Promise<Socket> {\n return new Promise((resolve, reject) => {\n const socket = connect(SOCK_PATH)\n socket.on('connect', () => {\n socket.removeAllListeners('error')\n resolve(socket)\n })\n socket.on('error', reject)\n })\n}\n\nasync function connectWithRetry(timeout: number): Promise<Socket> {\n const startTime = Date.now()\n let delay = CONNECT_RETRY_DELAY\n while (Date.now() - startTime < timeout) {\n try {\n return await connectHub()\n } catch (err: unknown) {\n if (\n err &&\n typeof err === 'object' &&\n 'code' in err &&\n (err.code === 'ENOENT' || err.code === 'ECONNREFUSED')\n ) {\n const remainingTime = timeout - (Date.now() - startTime)\n const waitTime = Math.min(delay, remainingTime)\n if (waitTime <= 0) break\n await new Promise((r) => setTimeout(r, waitTime))\n delay = Math.min(delay * 1.5, 1000)\n } else {\n throw err\n }\n }\n }\n throw new Error(`Failed to connect to Hub within ${timeout}ms.`)\n}\n\nfunction startHub(): ChildProcess {\n log.info('Spawning new Hub process...')\n return spawn(process.execPath, [HUB_ENTRY], {\n detached: true,\n stdio: 'ignore'\n })\n}\n\nasync function tryBecomeLeaderAndStartHub(): Promise<Socket> {\n let releaseLock: (() => Promise<void>) | null = null\n try {\n releaseLock = await lockfile.lock(LOCK_PATH, {\n retries: { retries: 5, factor: 1.2, minTimeout: 50 },\n stale: 15000\n })\n } catch {\n log.info('Another process is starting the Hub. Waiting...')\n return connectWithRetry(HUB_STARTUP_TIMEOUT)\n }\n\n log.info('Acquired lock. Starting Hub as the leader...')\n let child: ChildProcess | null = null\n try {\n try {\n return await connectHub()\n } catch {\n // If the Hub is not running, we proceed to start it.\n log.info('Hub not running. Proceeding to start it...')\n }\n child = startHub()\n child.on('error', (err) => log.error({ err }, 'Hub child process error.'))\n const socket = await connectWithRetry(HUB_STARTUP_TIMEOUT)\n child.unref()\n return socket\n } catch (err: unknown) {\n log.error({ err }, 'Failed to start or connect to the Hub.')\n if (child && !child.killed) {\n log.warn(`Killing stale Hub process (PID: ${child.pid})...`)\n child.kill('SIGTERM')\n }\n throw err\n } finally {\n if (releaseLock) await releaseLock()\n }\n}\n\nasync function main() {\n log.info({ version: PACKAGE_VERSION }, 'TemPad MCP Client starting...')\n\n while (true) {\n try {\n const socket = await connectHub().catch(() => {\n log.info('Hub not running. Initiating startup sequence...')\n return tryBecomeLeaderAndStartHub()\n })\n await bridge(socket)\n log.info('Bridge disconnected. Restarting connection process...')\n } catch (err: unknown) {\n log.error(\n { err },\n `Connection attempt failed. Retrying in ${FAILED_RESTART_DELAY / 1000}s...`\n )\n await new Promise((r) => setTimeout(r, FAILED_RESTART_DELAY))\n }\n }\n}\n\nmain()\n", "import { closeSync, mkdirSync, openSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport pino from 'pino'\n\nimport packageJson from '../package.json' assert { type: 'json' }\n\nexport function ensureDir(dirPath: string): void {\n mkdirSync(dirPath, { recursive: true, mode: 0o700 })\n}\n\nconst pkg = packageJson as { version?: unknown }\nexport const PACKAGE_VERSION = typeof pkg.version === 'string' ? pkg.version : '0.0.0'\n\nfunction resolveRuntimeDir(): string {\n if (process.env.TEMPAD_MCP_RUNTIME_DIR) return process.env.TEMPAD_MCP_RUNTIME_DIR\n return join(tmpdir(), 'tempad-dev', 'run')\n}\n\nfunction resolveLogDir(): string {\n if (process.env.TEMPAD_MCP_LOG_DIR) return process.env.TEMPAD_MCP_LOG_DIR\n return join(tmpdir(), 'tempad-dev', 'log')\n}\n\nfunction resolveAssetDir(): string {\n if (process.env.TEMPAD_MCP_ASSET_DIR) return process.env.TEMPAD_MCP_ASSET_DIR\n return join(tmpdir(), 'tempad-dev', 'assets')\n}\n\nexport const RUNTIME_DIR = resolveRuntimeDir()\nexport const LOG_DIR = resolveLogDir()\nexport const ASSET_DIR = resolveAssetDir()\n\nensureDir(RUNTIME_DIR)\nensureDir(LOG_DIR)\nensureDir(ASSET_DIR)\n\nexport function ensureFile(filePath: string): void {\n const fd = openSync(filePath, 'a')\n closeSync(fd)\n}\n\nexport const LOCK_PATH = join(RUNTIME_DIR, 'mcp.lock')\nensureFile(LOCK_PATH)\n\nconst timestamp = new Date().toISOString().replaceAll(':', '-').replaceAll('.', '-')\nconst pid = process.pid\nconst LOG_FILE = join(LOG_DIR, `mcp-${timestamp}-${pid}.log`)\n\nconst prettyTransport = pino.transport({\n target: 'pino-pretty',\n options: {\n translateTime: 'SYS:HH:MM:ss',\n destination: LOG_FILE\n }\n})\n\nexport const log = pino(\n {\n level: process.env.DEBUG ? 'debug' : 'info',\n msgPrefix: '[tempad-dev/mcp] '\n },\n prettyTransport\n)\n\nexport const SOCK_PATH =\n process.platform === 'win32' ? '\\\\\\\\.\\\\pipe\\\\tempad-mcp' : join(RUNTIME_DIR, 'mcp.sock')\n", "{\n \"name\": \"@tempad-dev/mcp\",\n \"description\": \"MCP server for TemPad Dev.\",\n \"version\": \"0.3.3\",\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\": \"tsc -p ./tsconfig.json --noEmit && node ./scripts/build.mjs\",\n \"prepublishOnly\": \"npm run build\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"^1.22.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 \"zod\": \"^4.1.12\"\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["join", "join"]
|
|
7
|
-
}
|