@tempad-dev/mcp 0.1.0
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/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/cli.js +199 -0
- package/dist/cli.js.map +7 -0
- package/dist/hub.js +418 -0
- package/dist/hub.js.map +7 -0
- package/package.json +24 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-Present Baidu EFE team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# @tempad-dev/mcp
|
|
2
|
+
|
|
3
|
+
## Usage
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"mcpServers": {
|
|
8
|
+
"TemPad Dev": {
|
|
9
|
+
"command": "npx",
|
|
10
|
+
"args": ["@tempad-dev/mcp"]
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
Optional environment variables:
|
|
19
|
+
|
|
20
|
+
- `TEMPAD_MCP_TOOL_TIMEOUT`: Tool call timeout in milliseconds (default `15000`).
|
|
21
|
+
- `TEMPAD_MCP_RUNTIME_DIR`: Override runtime directory (defaults to system temp under `tempad-dev/run`).
|
|
22
|
+
- `TEMPAD_MCP_LOG_DIR`: Override log directory (defaults to system temp under `tempad-dev/log`).
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- Node.js 18+
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
function ensureDir(dirPath) {
|
|
16
|
+
mkdirSync(dirPath, { recursive: true, mode: 448 });
|
|
17
|
+
}
|
|
18
|
+
function resolveRuntimeDir() {
|
|
19
|
+
if (process.env.TEMPAD_MCP_RUNTIME_DIR) return process.env.TEMPAD_MCP_RUNTIME_DIR;
|
|
20
|
+
return join(tmpdir(), "tempad-dev", "run");
|
|
21
|
+
}
|
|
22
|
+
function resolveLogDir() {
|
|
23
|
+
if (process.env.TEMPAD_MCP_LOG_DIR) return process.env.TEMPAD_MCP_LOG_DIR;
|
|
24
|
+
return join(tmpdir(), "tempad-dev", "log");
|
|
25
|
+
}
|
|
26
|
+
var RUNTIME_DIR = resolveRuntimeDir();
|
|
27
|
+
var LOG_DIR = resolveLogDir();
|
|
28
|
+
ensureDir(RUNTIME_DIR);
|
|
29
|
+
ensureDir(LOG_DIR);
|
|
30
|
+
function ensureFile(filePath) {
|
|
31
|
+
const fd = openSync(filePath, "a");
|
|
32
|
+
closeSync(fd);
|
|
33
|
+
}
|
|
34
|
+
var LOCK_PATH = join(RUNTIME_DIR, "mcp.lock");
|
|
35
|
+
ensureFile(LOCK_PATH);
|
|
36
|
+
var timestamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
|
|
37
|
+
var pid = process.pid;
|
|
38
|
+
var LOG_FILE = join(LOG_DIR, `mcp-${timestamp}-${pid}.log`);
|
|
39
|
+
var prettyTransport = pino.transport({
|
|
40
|
+
target: "pino-pretty",
|
|
41
|
+
options: {
|
|
42
|
+
translateTime: "SYS:HH:MM:ss",
|
|
43
|
+
destination: LOG_FILE
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
var log = pino(
|
|
47
|
+
{
|
|
48
|
+
level: process.env.DEBUG ? "debug" : "info",
|
|
49
|
+
msgPrefix: "[tempad-dev/mcp] "
|
|
50
|
+
},
|
|
51
|
+
prettyTransport
|
|
52
|
+
);
|
|
53
|
+
var SOCK_PATH = process.platform === "win32" ? "\\\\.\\pipe\\tempad-mcp" : join(RUNTIME_DIR, "mcp.sock");
|
|
54
|
+
|
|
55
|
+
// src/cli.ts
|
|
56
|
+
var activeSocket = null;
|
|
57
|
+
var shuttingDown = false;
|
|
58
|
+
function closeActiveSocket() {
|
|
59
|
+
if (!activeSocket) return;
|
|
60
|
+
try {
|
|
61
|
+
activeSocket.end();
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
activeSocket.destroy();
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
activeSocket = null;
|
|
69
|
+
}
|
|
70
|
+
function shutdownCli(reason) {
|
|
71
|
+
if (shuttingDown) return;
|
|
72
|
+
shuttingDown = true;
|
|
73
|
+
log.info(`${reason} Shutting down CLI.`);
|
|
74
|
+
closeActiveSocket();
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
process.on("SIGINT", () => shutdownCli("SIGINT received."));
|
|
78
|
+
process.on("SIGTERM", () => shutdownCli("SIGTERM received."));
|
|
79
|
+
var HUB_STARTUP_TIMEOUT = 5e3;
|
|
80
|
+
var CONNECT_RETRY_DELAY = 200;
|
|
81
|
+
var FAILED_RESTART_DELAY = 5e3;
|
|
82
|
+
var HERE = fileURLToPath(new URL(".", import.meta.url));
|
|
83
|
+
var HUB_ENTRY = join2(HERE, "hub.js");
|
|
84
|
+
ensureDir(RUNTIME_DIR);
|
|
85
|
+
function bridge(socket) {
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
log.info("Bridge established with Hub. Forwarding I/O.");
|
|
88
|
+
activeSocket = socket;
|
|
89
|
+
const onStdinEnd = () => {
|
|
90
|
+
shutdownCli("Consumer stream ended.");
|
|
91
|
+
};
|
|
92
|
+
process.stdin.once("end", onStdinEnd);
|
|
93
|
+
const onSocketClose = () => {
|
|
94
|
+
log.warn("Connection to Hub lost. Attempting to reconnect...");
|
|
95
|
+
activeSocket = null;
|
|
96
|
+
process.stdin.removeListener("end", onStdinEnd);
|
|
97
|
+
process.stdin.unpipe(socket);
|
|
98
|
+
socket.unpipe(process.stdout);
|
|
99
|
+
socket.removeAllListeners();
|
|
100
|
+
resolve();
|
|
101
|
+
};
|
|
102
|
+
socket.once("close", onSocketClose);
|
|
103
|
+
socket.on("error", (err) => log.warn({ err }, "Socket error occurred."));
|
|
104
|
+
process.stdin.pipe(socket, { end: false }).pipe(process.stdout);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function connectHub() {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const socket = connect(SOCK_PATH);
|
|
110
|
+
socket.on("connect", () => {
|
|
111
|
+
socket.removeAllListeners("error");
|
|
112
|
+
resolve(socket);
|
|
113
|
+
});
|
|
114
|
+
socket.on("error", reject);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async function connectWithRetry(timeout) {
|
|
118
|
+
const startTime = Date.now();
|
|
119
|
+
let delay = CONNECT_RETRY_DELAY;
|
|
120
|
+
while (Date.now() - startTime < timeout) {
|
|
121
|
+
try {
|
|
122
|
+
return await connectHub();
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (err && typeof err === "object" && "code" in err && (err.code === "ENOENT" || err.code === "ECONNREFUSED")) {
|
|
125
|
+
const remainingTime = timeout - (Date.now() - startTime);
|
|
126
|
+
const waitTime = Math.min(delay, remainingTime);
|
|
127
|
+
if (waitTime <= 0) break;
|
|
128
|
+
await new Promise((r) => setTimeout(r, waitTime));
|
|
129
|
+
delay = Math.min(delay * 1.5, 1e3);
|
|
130
|
+
} else {
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
throw new Error(`Failed to connect to Hub within ${timeout}ms.`);
|
|
136
|
+
}
|
|
137
|
+
function startHub() {
|
|
138
|
+
log.info("Spawning new Hub process...");
|
|
139
|
+
return spawn(process.execPath, [HUB_ENTRY], {
|
|
140
|
+
detached: true,
|
|
141
|
+
stdio: "ignore"
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
async function tryBecomeLeaderAndStartHub() {
|
|
145
|
+
let releaseLock = null;
|
|
146
|
+
try {
|
|
147
|
+
releaseLock = await lockfile.lock(LOCK_PATH, {
|
|
148
|
+
retries: { retries: 5, factor: 1.2, minTimeout: 50 },
|
|
149
|
+
stale: 15e3
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
log.info("Another process is starting the Hub. Waiting...");
|
|
153
|
+
return connectWithRetry(HUB_STARTUP_TIMEOUT);
|
|
154
|
+
}
|
|
155
|
+
log.info("Acquired lock. Starting Hub as the leader...");
|
|
156
|
+
let child = null;
|
|
157
|
+
try {
|
|
158
|
+
try {
|
|
159
|
+
return await connectHub();
|
|
160
|
+
} catch {
|
|
161
|
+
log.info("Hub not running. Proceeding to start it...");
|
|
162
|
+
}
|
|
163
|
+
child = startHub();
|
|
164
|
+
child.on("error", (err) => log.error({ err }, "Hub child process error."));
|
|
165
|
+
const socket = await connectWithRetry(HUB_STARTUP_TIMEOUT);
|
|
166
|
+
child.unref();
|
|
167
|
+
return socket;
|
|
168
|
+
} catch (err) {
|
|
169
|
+
log.error({ err }, "Failed to start or connect to the Hub.");
|
|
170
|
+
if (child && !child.killed) {
|
|
171
|
+
log.warn(`Killing stale Hub process (PID: ${child.pid})...`);
|
|
172
|
+
child.kill("SIGTERM");
|
|
173
|
+
}
|
|
174
|
+
throw err;
|
|
175
|
+
} finally {
|
|
176
|
+
if (releaseLock) await releaseLock();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function main() {
|
|
180
|
+
log.info("TemPad MCP Client starting...");
|
|
181
|
+
while (true) {
|
|
182
|
+
try {
|
|
183
|
+
const socket = await connectHub().catch(() => {
|
|
184
|
+
log.info("Hub not running. Initiating startup sequence...");
|
|
185
|
+
return tryBecomeLeaderAndStartHub();
|
|
186
|
+
});
|
|
187
|
+
await bridge(socket);
|
|
188
|
+
log.info("Bridge disconnected. Restarting connection process...");
|
|
189
|
+
} catch (err) {
|
|
190
|
+
log.error(
|
|
191
|
+
{ err },
|
|
192
|
+
`Connection attempt failed. Retrying in ${FAILED_RESTART_DELAY / 1e3}s...`
|
|
193
|
+
);
|
|
194
|
+
await new Promise((r) => setTimeout(r, FAILED_RESTART_DELAY));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
main();
|
|
199
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/cli.ts", "../src/shared.ts"],
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\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 { log, LOCK_PATH, RUNTIME_DIR, SOCK_PATH, ensureDir } from './shared'\n\nimport type { ChildProcess } from 'node:child_process'\nimport type { Socket } from 'node:net'\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 (error: unknown) {\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('TemPad MCP Client starting...')\n // eslint-disable-next-line no-constant-condition\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\nexport function ensureDir(dirPath: string): void {\n mkdirSync(dirPath, { recursive: true, mode: 0o700 })\n}\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\nexport const RUNTIME_DIR = resolveRuntimeDir()\nexport const LOG_DIR = resolveLogDir()\n\nensureDir(RUNTIME_DIR)\nensureDir(LOG_DIR)\n\nfunction 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()\n .toISOString()\n .replaceAll(':', '-')\n .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"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["join", "join"]
|
|
7
|
+
}
|
package/dist/hub.js
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
// src/hub.ts
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
5
|
+
import { existsSync, rmSync, chmodSync } from "node:fs";
|
|
6
|
+
import { createServer } from "node:net";
|
|
7
|
+
import { WebSocketServer } from "ws";
|
|
8
|
+
|
|
9
|
+
// src/request.ts
|
|
10
|
+
import { nanoid } from "nanoid";
|
|
11
|
+
|
|
12
|
+
// src/shared.ts
|
|
13
|
+
import { closeSync, mkdirSync, openSync } from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import pino from "pino";
|
|
17
|
+
function ensureDir(dirPath) {
|
|
18
|
+
mkdirSync(dirPath, { recursive: true, mode: 448 });
|
|
19
|
+
}
|
|
20
|
+
function resolveRuntimeDir() {
|
|
21
|
+
if (process.env.TEMPAD_MCP_RUNTIME_DIR) return process.env.TEMPAD_MCP_RUNTIME_DIR;
|
|
22
|
+
return join(tmpdir(), "tempad-dev", "run");
|
|
23
|
+
}
|
|
24
|
+
function resolveLogDir() {
|
|
25
|
+
if (process.env.TEMPAD_MCP_LOG_DIR) return process.env.TEMPAD_MCP_LOG_DIR;
|
|
26
|
+
return join(tmpdir(), "tempad-dev", "log");
|
|
27
|
+
}
|
|
28
|
+
var RUNTIME_DIR = resolveRuntimeDir();
|
|
29
|
+
var LOG_DIR = resolveLogDir();
|
|
30
|
+
ensureDir(RUNTIME_DIR);
|
|
31
|
+
ensureDir(LOG_DIR);
|
|
32
|
+
function ensureFile(filePath) {
|
|
33
|
+
const fd = openSync(filePath, "a");
|
|
34
|
+
closeSync(fd);
|
|
35
|
+
}
|
|
36
|
+
var LOCK_PATH = join(RUNTIME_DIR, "mcp.lock");
|
|
37
|
+
ensureFile(LOCK_PATH);
|
|
38
|
+
var timestamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
|
|
39
|
+
var pid = process.pid;
|
|
40
|
+
var LOG_FILE = join(LOG_DIR, `mcp-${timestamp}-${pid}.log`);
|
|
41
|
+
var prettyTransport = pino.transport({
|
|
42
|
+
target: "pino-pretty",
|
|
43
|
+
options: {
|
|
44
|
+
translateTime: "SYS:HH:MM:ss",
|
|
45
|
+
destination: LOG_FILE
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
var log = pino(
|
|
49
|
+
{
|
|
50
|
+
level: process.env.DEBUG ? "debug" : "info",
|
|
51
|
+
msgPrefix: "[tempad-dev/mcp] "
|
|
52
|
+
},
|
|
53
|
+
prettyTransport
|
|
54
|
+
);
|
|
55
|
+
var SOCK_PATH = process.platform === "win32" ? "\\\\.\\pipe\\tempad-mcp" : join(RUNTIME_DIR, "mcp.sock");
|
|
56
|
+
|
|
57
|
+
// src/request.ts
|
|
58
|
+
var pendingCalls = /* @__PURE__ */ new Map();
|
|
59
|
+
function register(extensionId, timeout) {
|
|
60
|
+
const requestId = nanoid();
|
|
61
|
+
const promise = new Promise((resolve2, reject2) => {
|
|
62
|
+
const timer = setTimeout(() => {
|
|
63
|
+
pendingCalls.delete(requestId);
|
|
64
|
+
reject2(new Error(`Extension did not respond within ${timeout / 1e3}s.`));
|
|
65
|
+
}, timeout);
|
|
66
|
+
pendingCalls.set(requestId, {
|
|
67
|
+
resolve: resolve2,
|
|
68
|
+
reject: reject2,
|
|
69
|
+
timer,
|
|
70
|
+
extensionId
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
return { promise, requestId };
|
|
74
|
+
}
|
|
75
|
+
function resolve(requestId, payload) {
|
|
76
|
+
const call = pendingCalls.get(requestId);
|
|
77
|
+
if (call) {
|
|
78
|
+
clearTimeout(call.timer);
|
|
79
|
+
call.resolve(payload);
|
|
80
|
+
pendingCalls.delete(requestId);
|
|
81
|
+
} else {
|
|
82
|
+
log.warn({ reqId: requestId }, "Received result for unknown/timed-out call.");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function reject(requestId, error) {
|
|
86
|
+
const call = pendingCalls.get(requestId);
|
|
87
|
+
if (call) {
|
|
88
|
+
clearTimeout(call.timer);
|
|
89
|
+
call.reject(error);
|
|
90
|
+
pendingCalls.delete(requestId);
|
|
91
|
+
} else {
|
|
92
|
+
log.warn({ reqId: requestId }, "Received error for unknown/timed-out call.");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function cleanupForExtension(extensionId) {
|
|
96
|
+
for (const [reqId, call] of pendingCalls.entries()) {
|
|
97
|
+
if (call.extensionId === extensionId) {
|
|
98
|
+
clearTimeout(call.timer);
|
|
99
|
+
call.reject(new Error("Extension disconnected before providing a result."));
|
|
100
|
+
pendingCalls.delete(reqId);
|
|
101
|
+
log.warn({ reqId, extId: extensionId }, "Rejected pending call from disconnected extension.");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function cleanupAll() {
|
|
106
|
+
pendingCalls.forEach((call, reqId) => {
|
|
107
|
+
clearTimeout(call.timer);
|
|
108
|
+
call.reject(new Error("Hub is shutting down."));
|
|
109
|
+
log.debug({ reqId }, "Rejected pending tool call due to shutdown.");
|
|
110
|
+
});
|
|
111
|
+
pendingCalls.clear();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/tools.ts
|
|
115
|
+
import { z } from "zod";
|
|
116
|
+
var GetCodeParametersSchema = z.object({
|
|
117
|
+
output: z.enum(["css", "js"]).optional().default("css")
|
|
118
|
+
});
|
|
119
|
+
var TOOLS = [
|
|
120
|
+
{
|
|
121
|
+
name: "get_code",
|
|
122
|
+
description: "Returns generated code for the currently selected node.",
|
|
123
|
+
parameters: GetCodeParametersSchema
|
|
124
|
+
}
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
// src/protocol.ts
|
|
128
|
+
import { z as z2 } from "zod";
|
|
129
|
+
var RegisteredMessageSchema = z2.object({
|
|
130
|
+
type: z2.literal("registered"),
|
|
131
|
+
id: z2.string()
|
|
132
|
+
});
|
|
133
|
+
var StateMessageSchema = z2.object({
|
|
134
|
+
type: z2.literal("state"),
|
|
135
|
+
activeId: z2.string().nullable(),
|
|
136
|
+
count: z2.number().nonnegative(),
|
|
137
|
+
port: z2.number().positive()
|
|
138
|
+
});
|
|
139
|
+
var ToolCallPayloadSchema = z2.object({
|
|
140
|
+
name: z2.string(),
|
|
141
|
+
args: z2.unknown()
|
|
142
|
+
});
|
|
143
|
+
var ToolCallMessageSchema = z2.object({
|
|
144
|
+
type: z2.literal("toolCall"),
|
|
145
|
+
id: z2.string(),
|
|
146
|
+
payload: ToolCallPayloadSchema
|
|
147
|
+
});
|
|
148
|
+
var MessageToExtensionSchema = z2.discriminatedUnion("type", [
|
|
149
|
+
RegisteredMessageSchema,
|
|
150
|
+
StateMessageSchema,
|
|
151
|
+
ToolCallMessageSchema
|
|
152
|
+
]);
|
|
153
|
+
var ActivateMessageSchema = z2.object({
|
|
154
|
+
type: z2.literal("activate")
|
|
155
|
+
});
|
|
156
|
+
var ToolResultMessageSchema = z2.object({
|
|
157
|
+
type: z2.literal("toolResult"),
|
|
158
|
+
id: z2.string(),
|
|
159
|
+
payload: z2.unknown().optional(),
|
|
160
|
+
error: z2.unknown().optional()
|
|
161
|
+
});
|
|
162
|
+
var MessageFromExtensionSchema = z2.discriminatedUnion("type", [
|
|
163
|
+
ActivateMessageSchema,
|
|
164
|
+
ToolResultMessageSchema
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
// src/hub.ts
|
|
168
|
+
function parsePositiveInt(env, fallback) {
|
|
169
|
+
const parsed = env ? Number.parseInt(env, 10) : Number.NaN;
|
|
170
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
171
|
+
}
|
|
172
|
+
var WS_PORT_CANDIDATES = [6220, 7431, 8127];
|
|
173
|
+
var TOOL_CALL_TIMEOUT = parsePositiveInt(process.env.TEMPAD_MCP_TOOL_TIMEOUT, 15e3);
|
|
174
|
+
var MAX_PAYLOAD_SIZE = 4 * 1024 * 1024;
|
|
175
|
+
var SHUTDOWN_TIMEOUT = 2e3;
|
|
176
|
+
var AUTO_ACTIVATE_GRACE_MS = parsePositiveInt(process.env.TEMPAD_MCP_AUTO_ACTIVATE_GRACE, 1500);
|
|
177
|
+
var extensions = [];
|
|
178
|
+
var consumerCount = 0;
|
|
179
|
+
var autoActivateTimer = null;
|
|
180
|
+
var selectedWsPort = 0;
|
|
181
|
+
var mcp = new McpServer({ name: "tempad-dev-mcp", version: "0.1.0" });
|
|
182
|
+
for (const tool of TOOLS) {
|
|
183
|
+
const schema = tool.parameters;
|
|
184
|
+
mcp.registerTool(
|
|
185
|
+
tool.name,
|
|
186
|
+
{
|
|
187
|
+
description: tool.description,
|
|
188
|
+
inputSchema: schema
|
|
189
|
+
},
|
|
190
|
+
async (args) => {
|
|
191
|
+
const parsedArgs = schema.parse(args);
|
|
192
|
+
const activeExt = extensions.find((e) => e.active);
|
|
193
|
+
if (!activeExt) throw new Error("No active TemPad Dev extension available.");
|
|
194
|
+
const { promise, requestId } = register(activeExt.id, TOOL_CALL_TIMEOUT);
|
|
195
|
+
const message = {
|
|
196
|
+
type: "toolCall",
|
|
197
|
+
id: requestId,
|
|
198
|
+
payload: {
|
|
199
|
+
name: tool.name,
|
|
200
|
+
args: parsedArgs
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
activeExt.ws.send(JSON.stringify(message));
|
|
204
|
+
log.info({ tool: tool.name, req: requestId, extId: activeExt.id }, "Forwarded tool call.");
|
|
205
|
+
const unknownPayload = await promise;
|
|
206
|
+
const textContent = typeof unknownPayload === "string" ? unknownPayload : JSON.stringify(unknownPayload, null, 2);
|
|
207
|
+
return { content: [{ type: "text", text: textContent }] };
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
log.info({ tools: TOOLS.map((t) => t.name) }, "Registered tools.");
|
|
212
|
+
function getActiveId() {
|
|
213
|
+
return extensions.find((e) => e.active)?.id ?? null;
|
|
214
|
+
}
|
|
215
|
+
function setActive(targetId) {
|
|
216
|
+
extensions.forEach((e) => {
|
|
217
|
+
e.active = targetId !== null && e.id === targetId;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function clearAutoActivateTimer() {
|
|
221
|
+
if (autoActivateTimer) {
|
|
222
|
+
clearTimeout(autoActivateTimer);
|
|
223
|
+
autoActivateTimer = null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function scheduleAutoActivate() {
|
|
227
|
+
clearAutoActivateTimer();
|
|
228
|
+
if (extensions.length !== 1 || getActiveId()) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const target = extensions[0];
|
|
232
|
+
autoActivateTimer = setTimeout(() => {
|
|
233
|
+
autoActivateTimer = null;
|
|
234
|
+
if (extensions.length === 1 && !getActiveId()) {
|
|
235
|
+
setActive(target.id);
|
|
236
|
+
log.info({ id: target.id }, "Auto-activated sole extension after grace period.");
|
|
237
|
+
broadcastState();
|
|
238
|
+
}
|
|
239
|
+
}, AUTO_ACTIVATE_GRACE_MS);
|
|
240
|
+
}
|
|
241
|
+
function broadcastState() {
|
|
242
|
+
const activeId = getActiveId();
|
|
243
|
+
const message = {
|
|
244
|
+
type: "state",
|
|
245
|
+
activeId,
|
|
246
|
+
count: extensions.length,
|
|
247
|
+
port: selectedWsPort
|
|
248
|
+
};
|
|
249
|
+
extensions.forEach((ext) => ext.ws.send(JSON.stringify(message)));
|
|
250
|
+
log.debug({ activeId, count: extensions.length }, "Broadcasted state.");
|
|
251
|
+
}
|
|
252
|
+
function shutdown() {
|
|
253
|
+
log.info("Hub is shutting down...");
|
|
254
|
+
netServer.close(() => log.info("Net server closed."));
|
|
255
|
+
wss?.close(() => log.info("WebSocket server closed."));
|
|
256
|
+
cleanupAll();
|
|
257
|
+
const timer = setTimeout(() => {
|
|
258
|
+
log.warn("Shutdown timed out. Forcing exit.");
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}, SHUTDOWN_TIMEOUT);
|
|
261
|
+
timer.unref();
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
ensureDir(RUNTIME_DIR);
|
|
265
|
+
if (process.platform !== "win32" && existsSync(SOCK_PATH)) {
|
|
266
|
+
log.warn({ sock: SOCK_PATH }, "Removing stale socket file.");
|
|
267
|
+
rmSync(SOCK_PATH);
|
|
268
|
+
}
|
|
269
|
+
} catch (error) {
|
|
270
|
+
log.error({ err: error }, "Failed to initialize runtime environment.");
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
var netServer = createServer((sock) => {
|
|
274
|
+
consumerCount++;
|
|
275
|
+
log.info(`Consumer connected. Total: ${consumerCount}`);
|
|
276
|
+
const transport = new StdioServerTransport(sock, sock);
|
|
277
|
+
mcp.connect(transport).catch((err) => {
|
|
278
|
+
log.error({ err }, "Failed to attach MCP transport.");
|
|
279
|
+
transport.close().catch((closeErr) => log.warn({ err: closeErr }, "Transport close failed."));
|
|
280
|
+
sock.destroy();
|
|
281
|
+
});
|
|
282
|
+
sock.on("error", (err) => {
|
|
283
|
+
log.warn({ err }, "Consumer socket error.");
|
|
284
|
+
transport.close().catch((closeErr) => log.warn({ err: closeErr }, "Transport close failed."));
|
|
285
|
+
});
|
|
286
|
+
sock.on("close", async () => {
|
|
287
|
+
await transport.close();
|
|
288
|
+
consumerCount--;
|
|
289
|
+
log.info(`Consumer disconnected. Remaining: ${consumerCount}`);
|
|
290
|
+
if (consumerCount === 0) {
|
|
291
|
+
log.info("Last consumer disconnected. Shutting down.");
|
|
292
|
+
shutdown();
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
netServer.on("error", (err) => {
|
|
297
|
+
log.error({ err }, "Net server error.");
|
|
298
|
+
process.exit(1);
|
|
299
|
+
});
|
|
300
|
+
netServer.listen(SOCK_PATH, () => {
|
|
301
|
+
try {
|
|
302
|
+
if (process.platform !== "win32") chmodSync(SOCK_PATH, 384);
|
|
303
|
+
} catch (err) {
|
|
304
|
+
log.error({ err }, "Failed to set socket permissions. Shutting down.");
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
log.info({ sock: SOCK_PATH }, "Hub socket ready.");
|
|
308
|
+
});
|
|
309
|
+
async function startWebSocketServer() {
|
|
310
|
+
for (const candidate of WS_PORT_CANDIDATES) {
|
|
311
|
+
const server = new WebSocketServer({
|
|
312
|
+
host: "127.0.0.1",
|
|
313
|
+
port: candidate,
|
|
314
|
+
maxPayload: MAX_PAYLOAD_SIZE
|
|
315
|
+
});
|
|
316
|
+
try {
|
|
317
|
+
await new Promise((resolve2, reject2) => {
|
|
318
|
+
const onError = (err) => {
|
|
319
|
+
server.off("listening", onListening);
|
|
320
|
+
reject2(err);
|
|
321
|
+
};
|
|
322
|
+
const onListening = () => {
|
|
323
|
+
server.off("error", onError);
|
|
324
|
+
resolve2();
|
|
325
|
+
};
|
|
326
|
+
server.once("error", onError);
|
|
327
|
+
server.once("listening", onListening);
|
|
328
|
+
});
|
|
329
|
+
return { wss: server, port: candidate };
|
|
330
|
+
} catch (err) {
|
|
331
|
+
server.close();
|
|
332
|
+
const errno = err;
|
|
333
|
+
if (errno.code === "EADDRINUSE") {
|
|
334
|
+
log.warn({ port: candidate }, "WebSocket port in use, trying next candidate.");
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
log.error({ err: errno, port: candidate }, "Failed to start WebSocket server.");
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
log.error(
|
|
342
|
+
{ candidates: WS_PORT_CANDIDATES },
|
|
343
|
+
"Unable to start WebSocket server on any candidate port."
|
|
344
|
+
);
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
var { wss, port } = await startWebSocketServer();
|
|
348
|
+
selectedWsPort = port;
|
|
349
|
+
wss.on("error", (err) => {
|
|
350
|
+
log.error({ err }, "WebSocket server critical error. Exiting.");
|
|
351
|
+
process.exit(1);
|
|
352
|
+
});
|
|
353
|
+
wss.on("connection", (ws) => {
|
|
354
|
+
const ext = { id: nanoid2(), ws, active: false };
|
|
355
|
+
extensions.push(ext);
|
|
356
|
+
log.info({ id: ext.id }, `Extension connected. Total: ${extensions.length}`);
|
|
357
|
+
const message = { type: "registered", id: ext.id };
|
|
358
|
+
ws.send(JSON.stringify(message));
|
|
359
|
+
broadcastState();
|
|
360
|
+
scheduleAutoActivate();
|
|
361
|
+
ws.on("message", (raw) => {
|
|
362
|
+
let messageBuffer;
|
|
363
|
+
if (Buffer.isBuffer(raw)) {
|
|
364
|
+
messageBuffer = raw;
|
|
365
|
+
} else if (raw instanceof ArrayBuffer) {
|
|
366
|
+
messageBuffer = Buffer.from(raw);
|
|
367
|
+
} else {
|
|
368
|
+
messageBuffer = Buffer.concat(raw);
|
|
369
|
+
}
|
|
370
|
+
let parsedJson;
|
|
371
|
+
try {
|
|
372
|
+
parsedJson = JSON.parse(messageBuffer.toString("utf-8"));
|
|
373
|
+
} catch (e) {
|
|
374
|
+
log.warn({ err: e, extId: ext.id }, "Failed to parse message.");
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const parseResult = MessageFromExtensionSchema.safeParse(parsedJson);
|
|
378
|
+
if (!parseResult.success) {
|
|
379
|
+
log.warn({ error: parseResult.error.flatten(), extId: ext.id }, "Invalid message shape.");
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const msg = parseResult.data;
|
|
383
|
+
switch (msg.type) {
|
|
384
|
+
case "activate": {
|
|
385
|
+
setActive(ext.id);
|
|
386
|
+
log.info({ id: ext.id }, "Extension activated.");
|
|
387
|
+
broadcastState();
|
|
388
|
+
scheduleAutoActivate();
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
case "toolResult": {
|
|
392
|
+
const { id, payload, error } = msg;
|
|
393
|
+
if (error) {
|
|
394
|
+
reject(id, error instanceof Error ? error : new Error(String(error)));
|
|
395
|
+
} else {
|
|
396
|
+
resolve(id, payload);
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
ws.on("close", () => {
|
|
403
|
+
const index = extensions.findIndex((e) => e.id === ext.id);
|
|
404
|
+
if (index > -1) extensions.splice(index, 1);
|
|
405
|
+
log.info({ id: ext.id }, `Extension disconnected. Remaining: ${extensions.length}`);
|
|
406
|
+
cleanupForExtension(ext.id);
|
|
407
|
+
if (ext.active) {
|
|
408
|
+
log.warn({ id: ext.id }, "Active extension disconnected.");
|
|
409
|
+
setActive(null);
|
|
410
|
+
}
|
|
411
|
+
broadcastState();
|
|
412
|
+
scheduleAutoActivate();
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
log.info({ port: selectedWsPort }, "WebSocket server ready.");
|
|
416
|
+
process.on("SIGINT", shutdown);
|
|
417
|
+
process.on("SIGTERM", shutdown);
|
|
418
|
+
//# sourceMappingURL=hub.js.map
|
package/dist/hub.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/hub.ts", "../src/request.ts", "../src/shared.ts", "../src/tools.ts", "../src/protocol.ts"],
|
|
4
|
+
"sourcesContent": ["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { nanoid } from 'nanoid'\nimport { existsSync, rmSync, chmodSync } from 'node:fs'\nimport { createServer } from 'node:net'\nimport { WebSocketServer } from 'ws'\n\nimport { register, resolve, reject, cleanupForExtension, cleanupAll } from './request'\nimport { log, RUNTIME_DIR, SOCK_PATH, ensureDir } from './shared'\nimport { TOOLS } from './tools'\nimport {\n MessageFromExtensionSchema,\n RegisteredMessage,\n StateMessage,\n ToolCallMessage,\n ToolResultMessage\n} from './protocol'\n\nimport type { RawData } from 'ws'\nimport type { ExtensionConnection } from './types'\n\nfunction parsePositiveInt(env: string | undefined, fallback: number): number {\n const parsed = env ? Number.parseInt(env, 10) : Number.NaN\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback\n}\n\nconst WS_PORT_CANDIDATES = [6220, 7431, 8127]\nconst TOOL_CALL_TIMEOUT = parsePositiveInt(process.env.TEMPAD_MCP_TOOL_TIMEOUT, 15000)\nconst MAX_PAYLOAD_SIZE = 4 * 1024 * 1024\nconst SHUTDOWN_TIMEOUT = 2000\nconst AUTO_ACTIVATE_GRACE_MS = parsePositiveInt(process.env.TEMPAD_MCP_AUTO_ACTIVATE_GRACE, 1500)\n\nconst extensions: ExtensionConnection[] = []\nlet consumerCount = 0\nlet autoActivateTimer: NodeJS.Timeout | null = null\nlet selectedWsPort = 0\n\nconst mcp = new McpServer({ name: 'tempad-dev-mcp', version: '0.1.0' })\n\nfor (const tool of TOOLS) {\n const schema = tool.parameters\n type InputSchema = Parameters<typeof mcp.registerTool>[1]['inputSchema']\n\n mcp.registerTool(\n tool.name,\n {\n description: tool.description,\n inputSchema: schema as unknown as InputSchema\n },\n async (args: unknown) => {\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<unknown>(activeExt.id, TOOL_CALL_TIMEOUT)\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 unknownPayload = await promise\n const textContent =\n typeof unknownPayload === 'string'\n ? unknownPayload\n : JSON.stringify(unknownPayload, null, 2)\n\n return { content: [{ type: 'text' as const, text: textContent }] }\n }\n )\n}\nlog.info({ tools: TOOLS.map((t) => t.name) }, 'Registered tools.')\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 }, AUTO_ACTIVATE_GRACE_MS)\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 }\n extensions.forEach((ext) => ext.ws.send(JSON.stringify(message)))\n log.debug({ activeId, count: extensions.length }, 'Broadcasted state.')\n}\n\nfunction shutdown(): void {\n log.info('Hub is shutting down...')\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 timer.unref()\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 WS_PORT_CANDIDATES) {\n const server = new WebSocketServer({\n host: '127.0.0.1',\n port: candidate,\n maxPayload: MAX_PAYLOAD_SIZE\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: WS_PORT_CANDIDATES },\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) => {\n let messageBuffer: Buffer\n if (Buffer.isBuffer(raw)) {\n messageBuffer = raw\n } else if (raw instanceof ArrayBuffer) {\n messageBuffer = Buffer.from(raw)\n } else {\n messageBuffer = Buffer.concat(raw)\n }\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", "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 clearTimeout(call.timer)\n call.resolve(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 clearTimeout(call.timer)\n call.reject(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 if (call.extensionId === extensionId) {\n clearTimeout(call.timer)\n call.reject(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 clearTimeout(call.timer)\n call.reject(new Error('Hub is shutting down.'))\n log.debug({ reqId }, 'Rejected pending tool call due to shutdown.')\n })\n pendingCalls.clear()\n}\n", "import { closeSync, mkdirSync, openSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport pino from 'pino'\n\nexport function ensureDir(dirPath: string): void {\n mkdirSync(dirPath, { recursive: true, mode: 0o700 })\n}\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\nexport const RUNTIME_DIR = resolveRuntimeDir()\nexport const LOG_DIR = resolveLogDir()\n\nensureDir(RUNTIME_DIR)\nensureDir(LOG_DIR)\n\nfunction 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()\n .toISOString()\n .replaceAll(':', '-')\n .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", "import { z } from 'zod'\n\nexport const GetCodeParametersSchema = z.object({\n output: z.enum(['css', 'js']).optional().default('css')\n})\n\nexport type GetCodeParametersInput = z.input<typeof GetCodeParametersSchema>\nexport type GetCodeResult = {\n code: Record<string, string>\n}\n\nexport const TOOLS = [\n {\n name: 'get_code',\n description: 'Returns generated code for the currently selected node.',\n parameters: GetCodeParametersSchema\n }\n] as const\n", "import { z } from 'zod'\n\n// Messages from hub to extension\nexport const RegisteredMessageSchema = z.object({\n type: z.literal('registered'),\n id: z.string()\n})\n\nexport const StateMessageSchema = z.object({\n type: z.literal('state'),\n activeId: z.string().nullable(),\n count: z.number().nonnegative(),\n port: z.number().positive()\n})\n\nexport const ToolCallPayloadSchema = z.object({\n name: z.string(),\n args: z.unknown()\n})\n\nexport const ToolCallMessageSchema = z.object({\n type: z.literal('toolCall'),\n id: z.string(),\n payload: ToolCallPayloadSchema\n})\n\nexport const MessageToExtensionSchema = z.discriminatedUnion('type', [\n RegisteredMessageSchema,\n StateMessageSchema,\n ToolCallMessageSchema\n])\n\n// Messages from extension to hub\nexport const ActivateMessageSchema = z.object({\n type: z.literal('activate')\n})\n\nexport const ToolResultMessageSchema = z.object({\n type: z.literal('toolResult'),\n id: z.string(),\n payload: z.unknown().optional(),\n error: z.unknown().optional()\n})\n\nexport const MessageFromExtensionSchema = z.discriminatedUnion('type', [\n ActivateMessageSchema,\n ToolResultMessageSchema\n])\n\nexport type RegisteredMessage = z.infer<typeof RegisteredMessageSchema>\nexport type StateMessage = z.infer<typeof StateMessageSchema>\nexport type ToolCallPayload = z.infer<typeof ToolCallPayloadSchema>\nexport type ToolCallMessage = z.infer<typeof ToolCallMessageSchema>\nexport type MessageToExtension = z.infer<typeof MessageToExtensionSchema>\nexport type ActivateMessage = z.infer<typeof ActivateMessageSchema>\nexport type ToolResultMessage = z.infer<typeof ToolResultMessageSchema>\nexport type MessageFromExtension = z.infer<typeof MessageFromExtensionSchema>\n\nexport function parseMessageToExtension(data: string): MessageToExtension | null {\n let parsed: unknown\n try {\n parsed = JSON.parse(data)\n } catch {\n return null\n }\n const result = MessageToExtensionSchema.safeParse(parsed)\n return result.success ? result.data : null\n}\n\nexport function parseMessageFromExtension(data: string): MessageFromExtension | null {\n let parsed: unknown\n try {\n parsed = JSON.parse(data)\n } catch {\n return null\n }\n const result = MessageFromExtensionSchema.safeParse(parsed)\n return result.success ? result.data : null\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["nanoid", "resolve", "reject", "z", "resolve", "reject", "nanoid"]
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tempad-dev/mcp",
|
|
3
|
+
"description": "MCP server for TemPad Dev.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/cli.js",
|
|
7
|
+
"bin": "dist/cli.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/**/*",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
14
|
+
"nanoid": "^5.1.6",
|
|
15
|
+
"pino": "^9.14.0",
|
|
16
|
+
"pino-pretty": "^11.2.2",
|
|
17
|
+
"proper-lockfile": "^4.1.2",
|
|
18
|
+
"ws": "^8.18.3",
|
|
19
|
+
"zod": "^4.1.12"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p ./tsconfig.json --noEmit && node ./scripts/build.mjs"
|
|
23
|
+
}
|
|
24
|
+
}
|