@classcad/mcp 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -87
- package/dist/app/SOURCE.txt +4 -0
- package/dist/app/assets/index-54a14bc0.css +8 -0
- package/dist/app/assets/index-c650c783.js +11257 -0
- package/dist/app/assets/troika-three-text.esm-188d86b5.js +220 -0
- package/dist/app/favicon.svg +98 -0
- package/dist/app/index.html +19 -0
- package/dist/app/manifest.json +14 -0
- package/dist/auth.d.ts +8 -0
- package/dist/auth.js +49 -10
- package/dist/auth.js.map +1 -1
- package/dist/backend.d.ts +8 -0
- package/dist/backend.js +39 -0
- package/dist/backend.js.map +1 -0
- package/dist/client.d.ts +48 -11
- package/dist/client.js +152 -167
- package/dist/client.js.map +1 -1
- package/dist/daemon.d.ts +0 -2
- package/dist/daemon.js +77 -62
- package/dist/daemon.js.map +1 -1
- package/dist/engine/key.d.ts +29 -0
- package/dist/engine/key.js +145 -0
- package/dist/engine/key.js.map +1 -0
- package/dist/engine/wasm-worker.js +7 -7
- package/dist/engine/wasm-worker.js.map +1 -1
- package/dist/engine/wasm.d.ts +16 -14
- package/dist/engine/wasm.js +22 -30
- package/dist/engine/wasm.js.map +1 -1
- package/dist/mcp-server.d.ts +4 -4
- package/dist/mcp-server.js +72 -28
- package/dist/mcp-server.js.map +1 -1
- package/dist/queue.js +1 -1
- package/dist/queue.js.map +1 -1
- package/dist/server.js +69 -36
- package/dist/server.js.map +1 -1
- package/dist/share/hub.d.ts +70 -0
- package/dist/share/hub.js +494 -0
- package/dist/share/hub.js.map +1 -0
- package/dist/share/protocol.d.ts +87 -0
- package/dist/share/protocol.js +54 -0
- package/dist/share/protocol.js.map +1 -0
- package/dist/share/relay.d.ts +45 -0
- package/dist/share/relay.js +230 -0
- package/dist/share/relay.js.map +1 -0
- package/dist/share/server.d.ts +27 -0
- package/dist/share/server.js +399 -0
- package/dist/share/server.js.map +1 -0
- package/dist/share/session.d.ts +120 -0
- package/dist/share/session.js +336 -0
- package/dist/share/session.js.map +1 -0
- package/dist/tools/auth.js +1 -1
- package/dist/tools/auth.js.map +1 -1
- package/dist/tools/docs.js +3 -2
- package/dist/tools/docs.js.map +1 -1
- package/dist/tools/lifecycle.js +36 -9
- package/dist/tools/lifecycle.js.map +1 -1
- package/dist/tools/share.d.ts +18 -0
- package/dist/tools/share.js +277 -0
- package/dist/tools/share.js.map +1 -0
- package/dist/tools/viewer.d.ts +1 -1
- package/dist/tools/viewer.js +11 -6
- package/dist/tools/viewer.js.map +1 -1
- package/package.json +4 -3
- package/dist/bridge/protocol.d.ts +0 -87
- package/dist/bridge/protocol.js +0 -14
- package/dist/bridge/protocol.js.map +0 -1
- package/dist/bridge/server.d.ts +0 -35
- package/dist/bridge/server.js +0 -236
- package/dist/bridge/server.js.map +0 -1
- package/dist/tools/bridge.d.ts +0 -4
- package/dist/tools/bridge.js +0 -115
- package/dist/tools/bridge.js.map +0 -1
package/dist/engine/wasm.js
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
// • the assets of one ClassCAD release (~16 MB compressed, ~85 MB unpacked: glue, main module, three
|
|
7
7
|
// side modules, class file, filter config), downloaded once from
|
|
8
8
|
// awvstatic.com into CLASSCAD_WASM_DIR (default ~/.classcad-mcp/wasm/<version>),
|
|
9
|
-
// • a ClassCAD key
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
9
|
+
// • a ClassCAD key for the origin the engine is told it runs on
|
|
10
|
+
// (http://localhost). The backend issues it for the signed-in account, or
|
|
11
|
+
// for CLASSCAD_TOKEN on CI (engine/key.ts); keys for local use name
|
|
12
|
+
// `localhost`, which the engine matches on any port.
|
|
13
13
|
//
|
|
14
|
-
// One engine = one drawing = one MCP session.
|
|
15
|
-
//
|
|
16
|
-
//
|
|
14
|
+
// One engine = one drawing = one MCP session. Apps dock into that session
|
|
15
|
+
// through share/hub.ts, which speaks a ClassCAD server's session protocol
|
|
16
|
+
// for this engine.
|
|
17
17
|
import { Worker } from 'node:worker_threads';
|
|
18
18
|
import { createWriteStream, existsSync, mkdirSync, renameSync, statSync, unlinkSync } from 'node:fs';
|
|
19
19
|
import { pipeline } from 'node:stream/promises';
|
|
@@ -21,27 +21,20 @@ import { Readable } from 'node:stream';
|
|
|
21
21
|
import { homedir } from 'node:os';
|
|
22
22
|
import { join, dirname } from 'node:path';
|
|
23
23
|
import { fileURLToPath } from 'node:url';
|
|
24
|
+
import { engineKey } from './key.js';
|
|
24
25
|
/** The ClassCAD release the MCP hosts (the same the buerli apps load). */
|
|
25
26
|
export const DEFAULT_WASM_VERSION = '21.2.0';
|
|
26
|
-
|
|
27
|
-
* The MCP's built-in key: wasm, enterprise, allowed origin http://localhost:3000,
|
|
28
|
-
* valid for six months (issued 2026-09-30). Built in so the local engine needs
|
|
29
|
-
* no configuration; CLASSCAD_WASM_KEY overrides it. Renew it before
|
|
30
|
-
* DEV_WASM_KEY_EXPIRES with buerli-backend/functions/scripts/appkey.mjs and
|
|
31
|
-
* ship a release: the engine refuses an expired key.
|
|
32
|
-
*/
|
|
33
|
-
export const DEV_WASM_KEY = 'MS4xLlZZUG51VkNpOGdjQm50RXB0VkE1RnQ1ekVVazNOR1dYMk9weHlxRGJjazRSdGYwTFRPTFl5NVdjYmY4VnFOOXlWTDQ0OUNzSExTbmhQRHZpNEVidXRubFJhZUpQVVA3aW1xZFBlWXlIcFRocFVlamVSZmJUaVM3aWcwWnlpdXNjSm5OaHFnTDhmN2EvSVRsZWxFQy9BRXJSQzZ5VUNLUC9UbFBiTTFINk10UlhNbkYwZm5QN2J0bE5DSWdWWW5RTldFSjFNcGYzaEJ4VjBjTHZlcW9tUXY0bklzVFFWSVl5T0hZVHM4ZU8vbENlaTZhdGxFbERycy9WVldoZHIwNm5DdWRvVEhTNGlmSGRWQ2FSak5QSmVNc1BmK1ZMYm5WVFI4aXl2MENadW1MbEsra2N3L0tqc0hLanZTUXJBTG5EQjcvOWpjcFhwbHlHNkFYRVppeVVESFZ0Ly9EL1VKdndMZ2E3S1ZFcFNRTStmS0xSZDR0RFYrVU41VHBrZ0wvYk5uK2huR3FHU2FtUExybXVGdXQ0T0EycHhiSlNZakZvYjFqbUZEbG5hVk1BTW11c1RqblVyQklGWGFxOUJDZnZRck13eHlGeE1RdEtCdGpnQUd5b1VSNmZmaFRXWVhpOGVtL2ZkU3hMNjFRamF4ejdLVExPS3pFOXJVMVgzcXBPQVo1NEFVenFVVzg0Zk9BeUZ0KzRMT2M0dzJMMWJ1U0YxUkZFby9CZXZ4UGRzQ3A4ZWUxMUxiTDRCQ0pFUEZJMGQyYU9JU0grN0NqOFZhVEpHb2JKVys3cktPemUyTTE4V3hOZnJMWVlhZXJzdDEwdDl1YTFja3lPdUZzRjMwdWFiejUvdGRVeENRRjhhcysxNFR2Slo4THowd1lxRkx2RTgrcG12N2VpT1ZUTzRtVSsvWXpLYkljVXFoNjNRdllkOU5jeWx3c0ZqRkZ4NGJlbGxrcHFuQjFNNHcvZVVmQ2VpNzRXT256ZGtNQzJiYWx2US9VWWJvS3ltbjZHVWtzbjZJczd0ZU5RMGs2TGovTm5mcFllcldNUXQxeEhjWnVySlZxd2hDRjBTTVRhSmhHWFNQODkwd3RFdkNRSS8zSHNYdUpseWFkWkpmZDZHRmJhNTRldEdoNUIveE80aWdNMEVuekVUMWVKa0tWMWhtbTduZjlZZzRwZmw4QnI5Z0ZZdFlPV080S2hWTjczYTB5Y0g5SlpMUUJydmlBVFowQm1uL3RNYnlKWitadmZjYzN1S2lvdXl4VC9ONlJwejlycFdrUmJvSHZSaHF4VkdjY3B4bG1nbnZudkNVaHZSZktFNTYvaDh0eEZXcXFncEo4VFR4cndFQVo3R0liRlVYVWN6TUhoQW9FV2lSWnVMTWxQck0zTVJWQkJ4YXA5a1BUeWVGaUFXU0tzZ2krUHRQbVp4ZTJldlRkekYzTEpvSkc4R25mQjZ5MVE4VWE5Q09wU3pMN05aamFpak9NRmU0dXBFaVI0andrOTUxMDVkcW9TTFExY0JoU2J0MytiNjkxcE1YYmNFZGNjU1ZYSnE5ejdubUJCeGs1THdUaURRdjdDTkQyZGNmejdKa0hXSENFcklvN2ZyMVFKU1V2dG5RRk5YUGxPNWhsTnZGVU9qWngwQkNDMGc2UDFrQUxVUEoweWtaZ3pQSnRYckdHL09XQjFUaERtTVNvMVpmdE1vbTlCdENjVGhMeGJuWDJmODlEcTNHZFE4RUd2UVV0cFRCNkVkeTdBOEE1aVNMemR1OEJhek5nYUwrclFlNk9CTko4VVEweEMzdmUrTFFoTmtVMTVzQWlycmtVdDJmL3JkMDM2bHd5d3ZnZTg2Umlvc1pnOVQ2RHVhNWRoQ1N3L0ZTQmpCOUo0QmI2Kys2TmNEOS9OTGFXNUIwUXJzdDRXYjR0cDEzL3Q3RXlxWEZiV3JLMVdiQlZtMnVhd3pzLzZ4RklR';
|
|
34
|
-
export const DEV_WASM_KEY_EXPIRES = '2027-03-30T09:25:07Z';
|
|
35
|
-
export const DEFAULT_WASM_ORIGIN = 'http://localhost:3000';
|
|
27
|
+
export const DEFAULT_WASM_ORIGIN = 'http://localhost';
|
|
36
28
|
/** Files of one release under https://awvstatic.com/classcad/download/release/<version>/wasm/. */
|
|
37
29
|
export const WASM_ASSETS = ['ClassCADWasm.js', 'ClassCADWasm.wasm', 'lgs2d.wasm', 'lgs3d.wasm', 'ExpWasm.wasm', 'classcad.cfe', 'filterconfig.json'];
|
|
38
|
-
/** Resolves the local engine settings from the environment
|
|
30
|
+
/** Resolves the local engine settings from the environment; the key is fetched at each engine start. */
|
|
39
31
|
export function wasmOptionsFromEnv(env = process.env) {
|
|
40
|
-
|
|
41
|
-
if (!key)
|
|
32
|
+
if (env.CLASSCAD_WASM === 'off')
|
|
42
33
|
return null;
|
|
43
34
|
return {
|
|
44
|
-
key
|
|
35
|
+
// A key of one's own wins; otherwise each engine start asks the backend
|
|
36
|
+
key: env.CLASSCAD_WASM_KEY || undefined,
|
|
37
|
+
getKey: () => engineKey(),
|
|
45
38
|
origin: env.CLASSCAD_WASM_ORIGIN,
|
|
46
39
|
version: env.CLASSCAD_WASM_VERSION,
|
|
47
40
|
dir: env.CLASSCAD_WASM_DIR,
|
|
@@ -88,8 +81,10 @@ export async function ensureWasmAssets(opts = {}) {
|
|
|
88
81
|
}
|
|
89
82
|
/** Downloads (if needed) and boots one engine in its own worker thread. */
|
|
90
83
|
export async function startLocalEngine(opts) {
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
const fetched = opts.key ? null : opts.getKey ? await opts.getKey() : null;
|
|
85
|
+
const key = opts.key ?? fetched?.key;
|
|
86
|
+
if (!key)
|
|
87
|
+
throw new Error('no ClassCAD key for the local WASM engine');
|
|
93
88
|
const version = opts.version ?? DEFAULT_WASM_VERSION;
|
|
94
89
|
const origin = opts.origin ?? DEFAULT_WASM_ORIGIN;
|
|
95
90
|
const log = opts.log ?? (() => { });
|
|
@@ -127,18 +122,15 @@ export async function startLocalEngine(opts) {
|
|
|
127
122
|
else if (m?.type === 'error') {
|
|
128
123
|
worker.off('message', onMessage);
|
|
129
124
|
// The engine refuses a bad key by exiting without a word: name the likely cause.
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
: opts.key !== DEV_WASM_KEY
|
|
134
|
-
? ' Check CLASSCAD_WASM_KEY: the engine refuses an expired key or one whose allowed origins do not include ' + origin + '.'
|
|
135
|
-
: '';
|
|
125
|
+
const hint = process.env.CLASSCAD_WASM_KEY
|
|
126
|
+
? ' Check CLASSCAD_WASM_KEY: the engine refuses an expired key or one whose allowed origins do not include ' + origin + '.'
|
|
127
|
+
: ' The engine refused its key; signing out and in again (`login` with logout, then `login`) fetches a new one.';
|
|
136
128
|
reject(new Error(`WASM engine failed to start: ${m.message}.${hint}`));
|
|
137
129
|
}
|
|
138
130
|
};
|
|
139
131
|
worker.on('message', onMessage);
|
|
140
132
|
worker.once('error', err => reject(new Error(`WASM engine failed to start: ${err.message}`)));
|
|
141
|
-
worker.postMessage({ type: 'init', dir, key
|
|
133
|
+
worker.postMessage({ type: 'init', dir, key, origin });
|
|
142
134
|
}).catch(err => {
|
|
143
135
|
worker.terminate().catch(() => { });
|
|
144
136
|
throw err;
|
package/dist/engine/wasm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wasm.js","sourceRoot":"","sources":["../../src/engine/wasm.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,0EAA0E;AAC1E,2CAA2C;AAC3C,EAAE;AACF,iBAAiB;AACjB,uGAAuG;AACvG,qEAAqE;AACrE,qFAAqF;AACrF,
|
|
1
|
+
{"version":3,"file":"wasm.js","sourceRoot":"","sources":["../../src/engine/wasm.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp-server.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { type Client, type EnginePolicy } from './client.js';
|
|
3
3
|
import { type LocalWasmOptions } from './engine/wasm.js';
|
|
4
|
-
import type
|
|
5
|
-
export declare const VERSION = "0.
|
|
4
|
+
import { type Show } from './share/session.js';
|
|
5
|
+
export declare const VERSION = "0.3.1";
|
|
6
6
|
export declare const DEFAULT_WS_URL = "ws://0.0.0.0:9094/";
|
|
7
7
|
export type McpServerOptions = {
|
|
8
8
|
/** ClassCAD worker this instance connects to (lazily, on the first tool call). */
|
|
9
9
|
wsUrl: string;
|
|
10
|
-
/** The shared bridge registry (apps announcing share tokens); null when no listener runs. */
|
|
11
|
-
bridge: () => BridgeRegistry | null;
|
|
12
10
|
/** Engine policy when nothing else decides: 'auto' (worker, else local WASM), 'drogon', 'wasm'. Default 'auto'. */
|
|
13
11
|
engine?: EnginePolicy;
|
|
14
12
|
/** Local WASM engine settings (key, origin, …); null = not available. */
|
|
15
13
|
wasm?: LocalWasmOptions | null;
|
|
14
|
+
/** How the session's app reaches the user (share/session.ts). Default: what this process's environment says. */
|
|
15
|
+
show?: Show;
|
|
16
16
|
/** Where the instance reports engine decisions. */
|
|
17
17
|
log?: (msg: string) => void;
|
|
18
18
|
};
|
package/dist/mcp-server.js
CHANGED
|
@@ -12,28 +12,46 @@ import { registerStateTools } from './tools/state.js';
|
|
|
12
12
|
import { DAEMON_NOTE, registerDocsTools, serverInstructions } from './tools/docs.js';
|
|
13
13
|
import { registerSnapshotTool } from './tools/snapshot.js';
|
|
14
14
|
import { registerScriptTool } from './tools/script.js';
|
|
15
|
-
import { registerBridgeTools } from './tools/bridge.js';
|
|
16
15
|
import { serializeTools } from './queue.js';
|
|
17
16
|
import { registerAuthTool, requireSignIn, SIGN_IN_NOTE } from './tools/auth.js';
|
|
18
17
|
import { authStatus } from './auth.js';
|
|
19
|
-
import { attachViewer,
|
|
20
|
-
|
|
18
|
+
import { attachViewer, VIEWER_NOTE } from './tools/viewer.js';
|
|
19
|
+
import { appNote, attachShare, registerSelectionTools, registerShareTool, registerViewTool } from './tools/share.js';
|
|
20
|
+
import { appAvailable, joinedHere, listen as listenForApps, waitForPage, SESSION_PATH } from './share/server.js';
|
|
21
|
+
import { sharingFromEnv, showFromEnv } from './share/session.js';
|
|
22
|
+
export const VERSION = '0.3.1';
|
|
23
|
+
/** True for the address sessions are joined on on this machine (share/server.ts): an app's own engine, or another agent's. */
|
|
24
|
+
const hostedOnThisMachine = (url) => {
|
|
25
|
+
try {
|
|
26
|
+
return new URL(url).pathname.replace(/\/+$/, '') === SESSION_PATH;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
21
32
|
export const DEFAULT_WS_URL = 'ws://0.0.0.0:9094/';
|
|
22
33
|
/** Builds a connected-ready MCP server: the engine client plus all tools. Nothing is opened yet. */
|
|
23
34
|
export async function createMcpServer(opts) {
|
|
24
|
-
const bridgeRegistry = opts.bridge();
|
|
25
35
|
// Build the client without opening the WS. The first tool call that needs
|
|
26
36
|
// the worker will open it — either use_session (with a named session) or
|
|
27
37
|
// any geometry tool (with no session header). This keeps the MCP passive
|
|
28
38
|
// at startup so it never creates a stray ephemeral session.
|
|
29
|
-
const client = await connect(opts.wsUrl, { graphics: true,
|
|
39
|
+
const client = await connect(opts.wsUrl, { graphics: true, engine: opts.engine, wasm: opts.wasm, log: opts.log });
|
|
40
|
+
// What the session shows the user: the app docked into it, where this build
|
|
41
|
+
// carries the app; the read-only 3D view otherwise.
|
|
42
|
+
const viewMode = process.env.CLASSCAD_VIEWER;
|
|
43
|
+
const withApp = (appAvailable() || !!process.env.CLASSCAD_APP_URL) && viewMode !== 'off' && viewMode !== 'readonly';
|
|
44
|
+
// How the app reaches the user: the agent is told from the first turn on, and again with the first model.
|
|
45
|
+
const show = opts.show ?? showFromEnv();
|
|
46
|
+
// Whether, and when, the session gets a link that works beyond this machine: only the app can be shared.
|
|
47
|
+
const sharing = withApp ? sharingFromEnv() : 'off';
|
|
30
48
|
const server = new McpServer({
|
|
31
49
|
name: 'classcad',
|
|
32
50
|
version: VERSION,
|
|
33
51
|
},
|
|
34
52
|
// The initialize-handshake instructions carry the full v1 method index —
|
|
35
53
|
// hosts surface them to the agent, so it knows every method from turn one.
|
|
36
|
-
{ instructions: `${SIGN_IN_NOTE}\n\n${VIEWER_NOTE}\n\n${serverInstructions()}` });
|
|
54
|
+
{ instructions: `${SIGN_IN_NOTE}\n\n${withApp ? appNote(show, sharing) : VIEWER_NOTE}\n\n${serverInstructions()}` });
|
|
37
55
|
// One tool call at a time: every tool below shares this client's single
|
|
38
56
|
// engine connection and drawing (see queue.ts for the reasoning, incl.
|
|
39
57
|
// subagents). Must run before the first registerTool.
|
|
@@ -42,19 +60,26 @@ export async function createMcpServer(opts) {
|
|
|
42
60
|
// Registered after the queue patch, so the check runs before queueing.
|
|
43
61
|
requireSignIn(server);
|
|
44
62
|
registerAuthTool(server);
|
|
45
|
-
// The session's
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
63
|
+
// The session's company: the app docked into it, whoever else is in it, and
|
|
64
|
+
// what they have selected. Patched in last, so it sits innermost: it hears
|
|
65
|
+
// of a change inside the queue, after the tool that made it.
|
|
66
|
+
const share = await attachShare(server, client, VERSION, show, sharing, opts.log);
|
|
67
|
+
const viewer = share.url ? null : await attachViewer(server, client, opts.log);
|
|
68
|
+
registerViewTool(server, share, viewer);
|
|
69
|
+
registerShareTool(server, share);
|
|
70
|
+
registerSelectionTools(server, client, share);
|
|
49
71
|
server.registerTool('session_info', {
|
|
50
72
|
title: 'Session info',
|
|
51
|
-
description: 'Return ClassCAD MCP session status: the link of the
|
|
73
|
+
description: 'Return ClassCAD MCP session status: the link of the app docked into this session (app) or of the read-only 3D view (viewer), the link the session is shared with others under (shared; null while it is not), who else is in the session (peers), sign-in (auth), transport (ws = ClassCAD worker, wasm = the MCP\'s own local engine), engine policy, whether a local WASM engine is available, WS URL, current session id, the invite this session joined with (shareToken), connection state, package version. ' + DAEMON_NOTE,
|
|
52
74
|
inputSchema: {},
|
|
53
75
|
}, async () => {
|
|
54
76
|
const local = client.localEngine;
|
|
55
77
|
const auth = await authStatus();
|
|
56
78
|
const info = {
|
|
79
|
+
app: share.url,
|
|
57
80
|
viewer: viewer?.url ?? null,
|
|
81
|
+
shared: share.sharedUrl,
|
|
82
|
+
peers: share.peers().map(p => ({ app: p.identity?.app ?? null, name: p.identity?.name ?? p.inviteName ?? null, kind: p.identity?.kind ?? null, role: p.role ?? null })),
|
|
58
83
|
auth: auth.signedIn ? { signedIn: true, email: auth.account.email, ...(auth.offline ? { offline: true } : {}) } : { signedIn: false, reason: auth.reason },
|
|
59
84
|
transport: client.transport,
|
|
60
85
|
enginePolicy: client.engine,
|
|
@@ -76,7 +101,7 @@ export async function createMcpServer(opts) {
|
|
|
76
101
|
});
|
|
77
102
|
server.registerTool('use_session', {
|
|
78
103
|
title: 'Switch session',
|
|
79
|
-
description: 'Call this BEFORE any other classcad tool when the user names a session or hands you a session/token URL — the MCP opens its engine link lazily, so the first tool call decides which session is used. Modes: (1) sessionId — attaches to a named ClassCAD session (ClassCAD-Session-Id header) on the configured worker; empty/omitted = fresh worker-assigned session. (2) url — a ws(s):// URL from a multi-client server, typically carrying an invite token (wss://host/?invite=…); connected VERBATIM. (3) an http(s):// APP SHARE LINK from a buerli app\'s Session Management
|
|
104
|
+
description: 'Call this BEFORE any other classcad tool when the user names a session or hands you a session/token URL — the MCP opens its engine link lazily, so the first tool call decides which session is used. Modes: (1) sessionId — attaches to a named ClassCAD session (ClassCAD-Session-Id header) on the configured worker; empty/omitted = fresh worker-assigned session. (2) url — a ws(s):// URL from a multi-client server, typically carrying an invite token (wss://host/?invite=…); connected VERBATIM. (3) an http(s):// APP SHARE LINK from a buerli app\'s Session Management (…?invite=…): the MCP joins the app\'s session as a guest, wherever its engine runs — on a ClassCAD server (the token is applied to the configured worker URL) or in the app\'s own page (WASM; the app must stay open, on this machine). Without a url the MCP runs its own session — and the user\'s app docks into THAT with the link `view` returns. (4) engine — WHICH ENGINE runs that own session: "drogon" = the configured ClassCAD worker (server), "wasm" = the MCP\'s OWN local engine (the published WASM build hosted in this process, no server and no browser needed; requires a configured key), "auto" (default) = the worker if reachable, otherwise the local engine. Use "drogon" when the user says to connect to their (Drogon/ClassCAD) server, "wasm" when they say WASM/local/offline; a token or URL always decides by itself. Reconnecting clears cached structure/graphic state — the next tool call repopulates it.',
|
|
80
105
|
inputSchema: {
|
|
81
106
|
sessionId: z.string().optional()
|
|
82
107
|
.describe('Target session id (named-session model, worker only). Empty string or omitted = no header (worker-assigned session).'),
|
|
@@ -89,27 +114,49 @@ export async function createMcpServer(opts) {
|
|
|
89
114
|
const target = sessionId && sessionId.length > 0 ? sessionId : null;
|
|
90
115
|
try {
|
|
91
116
|
let wsUrl = url && url.length > 0 ? url : null;
|
|
117
|
+
let joined = false;
|
|
92
118
|
// App share links (http/https) point at the WEB APP, not the CAD
|
|
93
119
|
// server — carry over only the invite token onto the worker base URL.
|
|
94
120
|
if (wsUrl && /^https?:\/\//i.test(wsUrl)) {
|
|
95
121
|
const params = new URL(wsUrl).searchParams;
|
|
96
|
-
|
|
97
|
-
if (bridge) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
content: [{ type: 'text', text: JSON.stringify({ ok: true, transport: 'bridge', shareToken: bridge }) }],
|
|
102
|
-
};
|
|
122
|
+
// Links of apps that run the engine in their own page (older buerli builds): there is no session to join.
|
|
123
|
+
if (params.get('bridge')) {
|
|
124
|
+
throw new Error('This link is of an app that runs ClassCAD in its own page; such an engine cannot be joined. ' +
|
|
125
|
+
'Work the other way round: build in this MCP session and let the user open its app with `view` — it docks into this session.');
|
|
103
126
|
}
|
|
104
127
|
const invite = params.get('invite');
|
|
105
128
|
if (!invite) {
|
|
106
|
-
throw new Error(`"${wsUrl}" is an http(s) app link without an ?invite=
|
|
107
|
-
'Pass the ws(s):// URL of the ClassCAD server (optionally with ?invite=…), or an app share link that carries ?invite
|
|
129
|
+
throw new Error(`"${wsUrl}" is an http(s) app link without an ?invite= token. ` +
|
|
130
|
+
'Pass the ws(s):// URL of the ClassCAD server (optionally with ?invite=…), or an app share link that carries ?invite=.');
|
|
108
131
|
}
|
|
109
|
-
|
|
110
|
-
|
|
132
|
+
// Whose session the invite is for, the link does not say. An app that
|
|
133
|
+
// runs the engine in its own page offers its invites on this machine's
|
|
134
|
+
// listener, and so does every MCP session; any other invite is a
|
|
135
|
+
// ClassCAD server's.
|
|
136
|
+
const local = async () => `ws://127.0.0.1:${(await listenForApps()).port}${SESSION_PATH}/?invite=${encodeURIComponent(invite)}`;
|
|
137
|
+
if (joinedHere(invite))
|
|
138
|
+
wsUrl = await local();
|
|
139
|
+
else {
|
|
140
|
+
const base = client.baseUrl.replace(/\/+$/, '');
|
|
141
|
+
try {
|
|
142
|
+
await client.reconnectUrl(`${base}/?invite=${encodeURIComponent(invite)}`);
|
|
143
|
+
wsUrl = null;
|
|
144
|
+
joined = true;
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
// Not the server's: a page may be about to offer it (it finds a restarted MCP within seconds).
|
|
148
|
+
if (!(await waitForPage(invite, 8_000))) {
|
|
149
|
+
throw new Error(`Nobody shares a session under this invite: the ClassCAD server at ${base} refused it (${err instanceof Error ? err.message : err}), ` +
|
|
150
|
+
'and no app on this machine offers it. Is the app still open, and is the invite still there in its session panel?');
|
|
151
|
+
}
|
|
152
|
+
wsUrl = await local();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (joined) {
|
|
157
|
+
/* already in the server's session */
|
|
111
158
|
}
|
|
112
|
-
if (wsUrl)
|
|
159
|
+
else if (wsUrl)
|
|
113
160
|
await client.reconnectUrl(wsUrl);
|
|
114
161
|
else
|
|
115
162
|
await client.reconnect(target, engine);
|
|
@@ -127,7 +174,7 @@ export async function createMcpServer(opts) {
|
|
|
127
174
|
text: JSON.stringify({
|
|
128
175
|
ok: true,
|
|
129
176
|
transport: client.transport,
|
|
130
|
-
engine: client.transport === 'wasm' ? 'wasm (local, in this process)' : 'drogon (ClassCAD worker)',
|
|
177
|
+
engine: client.transport === 'wasm' ? 'wasm (local, in this process)' : hostedOnThisMachine(client.url) ? 'the engine of the app that shared the session (it must stay open)' : 'drogon (ClassCAD worker)',
|
|
131
178
|
sessionId: client.sessionId,
|
|
132
179
|
wsUrl: client.transport === 'ws' ? client.url : null,
|
|
133
180
|
}),
|
|
@@ -139,9 +186,6 @@ export async function createMcpServer(opts) {
|
|
|
139
186
|
registerDocsTools(server);
|
|
140
187
|
registerSnapshotTool(server, client);
|
|
141
188
|
registerScriptTool(server, client);
|
|
142
|
-
// Bridge tools (app-state read/write) — only when the listener is up.
|
|
143
|
-
if (bridgeRegistry)
|
|
144
|
-
registerBridgeTools(server, client, bridgeRegistry);
|
|
145
189
|
return { server, client };
|
|
146
190
|
}
|
|
147
191
|
//# sourceMappingURL=mcp-server.js.map
|
package/dist/mcp-server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,0EAA0E;AAC1E,8EAA8E;AAC9E,sEAAsE;AACtE,kCAAkC;AAElC,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACnE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,OAAO,EAAkC,MAAM,aAAa,CAAA;AACrE,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,cAAc,EAAyB,MAAM,kBAAkB,CAAA;AACnH,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
|
package/dist/queue.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
/** Tools that only read package data (no engine access) and stay unqueued. */
|
|
24
24
|
const busyChecks = new WeakMap();
|
|
25
25
|
export function setDrawingBusy(server, check) { busyChecks.set(server, check); }
|
|
26
|
-
export const UNQUEUED_TOOLS = new Set(['list_methods', 'describe_method', 'docs', 'session_info', 'view']);
|
|
26
|
+
export const UNQUEUED_TOOLS = new Set(['list_methods', 'describe_method', 'docs', 'session_info', 'view', 'share']);
|
|
27
27
|
/**
|
|
28
28
|
* Patches `server.registerTool` so every handler registered afterwards runs
|
|
29
29
|
* through the shared queue (except the tools in `exclude`). Call it once,
|
package/dist/queue.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,qEAAqE;AACrE,8EAA8E;AAC9E,4EAA4E;AAC5E,2EAA2E;AAC3E,8EAA8E;AAC9E,8EAA8E;AAC9E,0EAA0E;AAC1E,uDAAuD;AACvD,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AACzE,2EAA2E;AAC3E,kCAAkC;AAClC,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,2EAA2E;AAC3E,yEAAyE;AACzE,6EAA6E;AAC7E,sBAAsB;AAItB,8EAA8E;AAC9E,MAAM,UAAU,GAAG,IAAI,OAAO,EAA4B,CAAA;AAC1D,MAAM,UAAU,cAAc,CAAC,MAAiB,EAAE,KAAoB,IAAU,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA,CAAC,CAAC;AAE/G,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,iBAAiB,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,qEAAqE;AACrE,8EAA8E;AAC9E,4EAA4E;AAC5E,2EAA2E;AAC3E,8EAA8E;AAC9E,8EAA8E;AAC9E,0EAA0E;AAC1E,uDAAuD;AACvD,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AACzE,2EAA2E;AAC3E,kCAAkC;AAClC,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,2EAA2E;AAC3E,yEAAyE;AACzE,6EAA6E;AAC7E,sBAAsB;AAItB,8EAA8E;AAC9E,MAAM,UAAU,GAAG,IAAI,OAAO,EAA4B,CAAA;AAC1D,MAAM,UAAU,cAAc,CAAC,MAAiB,EAAE,KAAoB,IAAU,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA,CAAC,CAAC;AAE/G,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,iBAAiB,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAEnH;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAAiB,EAAE,UAAuB,cAAc;IACrF,8EAA8E;IAC9E,IAAI,IAAI,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAA;IAC9C,MAAM,IAAI,GAAG,CAAI,IAA0B,EAAc,EAAE;QACzD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3B,oEAAoE;QACpE,IAAI,GAAG,GAAG,CAAC,IAAI,CACb,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,EAAE,CAAC,SAAS,CAChB,CAAA;QACD,OAAO,GAAG,CAAA;IACZ,CAAC,CAAA;IACD,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IACxB,MAAM,QAAQ,GAAI,MAAM,CAAC,YAAwC,CAAC,IAAI,CAAC,MAAM,CAAC,CAC7E;IAAC,MAAc,CAAC,YAAY,GAAG,CAAC,IAAY,EAAE,MAAe,EAAE,OAAgC,EAAE,EAAE;QAClG,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;QAC7D,MAAM,MAAM,GAAG,CAAC,GAAG,IAAW,EAAE,EAAE,CAChC,IAAI,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE;YACnC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,6FAA6F,EAAE,CAAC,EAAE;YACrJ,CAAC,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAAA;QACvB,OAAO,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,CAAC,CAAA;AACH,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,OAAO,EAA4D,CAAA;AAEtF;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAI,MAAiB,EAAE,IAA0B;IACtE,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/B,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzD,CAAC"}
|
package/dist/server.js
CHANGED
|
@@ -4,13 +4,16 @@
|
|
|
4
4
|
// It is a thin shim: it finds the ClassCAD MCP daemon on this machine (or
|
|
5
5
|
// starts it, detached) and forwards its host's JSON-RPC to the daemon over
|
|
6
6
|
// HTTP (MCP Streamable HTTP). Every tab therefore gets its own session in the
|
|
7
|
-
// ONE daemon process
|
|
7
|
+
// ONE daemon process — see daemon.ts for why.
|
|
8
8
|
//
|
|
9
9
|
// If no daemon can be reached at all (the port is held by a foreign program
|
|
10
|
-
// and cannot be freed), the shim serves the MCP in-process
|
|
11
|
-
// the
|
|
10
|
+
// and cannot be freed), the shim serves the MCP in-process: the same MCP, with
|
|
11
|
+
// a listener of its own for the app (on a free port if the usual one is taken).
|
|
12
12
|
import { spawn } from 'node:child_process';
|
|
13
|
+
import { request as httpRequest } from 'node:http';
|
|
14
|
+
import { request as httpsRequest } from 'node:https';
|
|
13
15
|
import { connect as tcpConnect } from 'node:net';
|
|
16
|
+
import { homedir } from 'node:os';
|
|
14
17
|
import { fileURLToPath } from 'node:url';
|
|
15
18
|
import { dirname, join } from 'node:path';
|
|
16
19
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -18,6 +21,7 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
|
|
|
18
21
|
import { createMcpServer, DEFAULT_WS_URL, VERSION } from './mcp-server.js';
|
|
19
22
|
import { DAEMON_HOST, DEFAULT_DAEMON_PORT, defaultDaemonLogFile, daemonBuildStamp } from './daemon.js';
|
|
20
23
|
import { wasmOptionsFromEnv } from './engine/wasm.js';
|
|
24
|
+
import { showFromEnv } from './share/session.js';
|
|
21
25
|
import { authStatus, beginLogin, logout, waitForLogin } from './auth.js';
|
|
22
26
|
const WS_URL = process.env.CLASSCAD_WS_URL ?? DEFAULT_WS_URL;
|
|
23
27
|
/** Engine policy for this tab's session: auto (worker, else local WASM) | drogon | wasm. */
|
|
@@ -28,12 +32,43 @@ const DAEMON_PORT = Number(process.env.CLASSCAD_MCP_PORT ?? DEFAULT_DAEMON_PORT)
|
|
|
28
32
|
const DAEMON_URL = process.env.CLASSCAD_MCP_URL ?? `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
29
33
|
const SPAWN_TIMEOUT_MS = 10_000;
|
|
30
34
|
const log = (msg) => process.stderr.write(`[classcad-mcp] ${msg}\n`);
|
|
35
|
+
/**
|
|
36
|
+
* One question to the daemon (is it there, please go), on a connection of its
|
|
37
|
+
* own, answered or given up within `timeoutMs`: null when nobody answered.
|
|
38
|
+
*
|
|
39
|
+
* Plain http, not fetch. fetch keeps its connections: one that sat idle answers
|
|
40
|
+
* late on some Node versions (seen: up to 1.5 s on 26.7, which reads as "no
|
|
41
|
+
* daemon"). And told to close the connection, it may never settle when the
|
|
42
|
+
* other side hangs up at once (seen on Node 20, with a foreign program on the
|
|
43
|
+
* port): with nothing else left to wait for, the process then simply ends.
|
|
44
|
+
*/
|
|
45
|
+
function ask(method, path, timeoutMs = 1500) {
|
|
46
|
+
return new Promise(resolve => {
|
|
47
|
+
const url = new URL(`${DAEMON_URL}${path}`);
|
|
48
|
+
const send = url.protocol === 'https:' ? httpsRequest : httpRequest;
|
|
49
|
+
const settle = (answer) => {
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
resolve(answer);
|
|
52
|
+
};
|
|
53
|
+
const req = send(url, { method, agent: false }, res => {
|
|
54
|
+
const chunks = [];
|
|
55
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
56
|
+
res.on('end', () => settle({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString('utf8') }));
|
|
57
|
+
res.on('error', () => settle(null));
|
|
58
|
+
});
|
|
59
|
+
const timer = setTimeout(() => req.destroy(new Error('no answer in time')), timeoutMs);
|
|
60
|
+
req.on('error', () => settle(null));
|
|
61
|
+
req.end();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** True for an answer in the 200s. */
|
|
65
|
+
const answeredOk = (res) => !!res && res.status >= 200 && res.status < 300;
|
|
31
66
|
async function health() {
|
|
67
|
+
const res = await ask('GET', '/health');
|
|
68
|
+
if (!answeredOk(res))
|
|
69
|
+
return null;
|
|
32
70
|
try {
|
|
33
|
-
const
|
|
34
|
-
if (!res.ok)
|
|
35
|
-
return null;
|
|
36
|
-
const j = (await res.json());
|
|
71
|
+
const j = JSON.parse(res.body);
|
|
37
72
|
return j && j.name === 'classcad-mcp' ? j : null;
|
|
38
73
|
}
|
|
39
74
|
catch {
|
|
@@ -61,6 +96,8 @@ async function spawnDaemon() {
|
|
|
61
96
|
const daemonPath = join(dirname(fileURLToPath(import.meta.url)), 'daemon.js');
|
|
62
97
|
const child = spawn(process.execPath, [daemonPath], {
|
|
63
98
|
detached: true,
|
|
99
|
+
// Not this shim's folder, which can go away while the daemon still runs (see daemon.ts).
|
|
100
|
+
cwd: homedir(),
|
|
64
101
|
stdio: 'ignore',
|
|
65
102
|
windowsHide: true,
|
|
66
103
|
env: {
|
|
@@ -101,29 +138,19 @@ async function ensureDaemon() {
|
|
|
101
138
|
if (h && stale(h)) {
|
|
102
139
|
// An older daemon (previous install or previous build) — ask it to leave when idle.
|
|
103
140
|
log(`daemon ${h.version} (build ${h.build ?? '?'}) running, this MCP is ${VERSION} (build ${daemonBuildStamp()}): asking it to shut down`);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
h = await health();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
/* fall through: use it as is */
|
|
141
|
+
// No answer, or a refusal (it is busy): fall through and use it as is.
|
|
142
|
+
if (answeredOk(await ask('POST', '/shutdown'))) {
|
|
143
|
+
for (let i = 0; i < 25 && (await health()); i++)
|
|
144
|
+
await sleep(200);
|
|
145
|
+
h = await health();
|
|
114
146
|
}
|
|
115
147
|
if (h && stale(h)) {
|
|
116
148
|
// Busy with other tabs. Tell it to drain (newer daemons stop taking
|
|
117
149
|
// sessions and exit after the last one) and run THIS tab on the new
|
|
118
|
-
// code in-process
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
/* an old daemon without drain support: it leaves when idle */
|
|
125
|
-
}
|
|
126
|
-
log(`daemon ${h.version} (build ${h.build ?? '?'}) still serves ${h.sessions ?? '?'} other tab(s) — this tab runs the current build in-process (no bridge listener until that daemon is gone; restart the other tabs to move them)`);
|
|
150
|
+
// code in-process. The next tab after the old one is gone starts a fresh daemon.
|
|
151
|
+
// An old daemon without drain support leaves when idle.
|
|
152
|
+
await ask('POST', '/shutdown?drain=1');
|
|
153
|
+
log(`daemon ${h.version} (build ${h.build ?? '?'}) still serves ${h.sessions ?? '?'} other tab(s) — this tab runs the current build in-process (restart the other tabs to move them)`);
|
|
127
154
|
return null;
|
|
128
155
|
}
|
|
129
156
|
}
|
|
@@ -168,7 +195,11 @@ async function proxyToDaemon() {
|
|
|
168
195
|
headers: {
|
|
169
196
|
'x-classcad-ws-url': WS_URL,
|
|
170
197
|
'x-classcad-engine': ENGINE,
|
|
171
|
-
|
|
198
|
+
// How this host shows the session's app: its own browser pane, or the user's browser.
|
|
199
|
+
'x-classcad-show': showFromEnv(),
|
|
200
|
+
// A key of this host's own (CLASSCAD_WASM_KEY); without one the daemon fetches the session's key from the sign-in
|
|
201
|
+
...(WASM?.key ? { 'x-classcad-wasm-key': WASM.key } : {}),
|
|
202
|
+
...(WASM?.origin ? { 'x-classcad-wasm-origin': WASM.origin } : {}),
|
|
172
203
|
},
|
|
173
204
|
},
|
|
174
205
|
});
|
|
@@ -291,9 +322,9 @@ async function proxyToDaemon() {
|
|
|
291
322
|
process.stdin.on('close', () => void finish('host closed stdin'));
|
|
292
323
|
await stdio.start();
|
|
293
324
|
}
|
|
294
|
-
/** Last resort: the MCP in this process
|
|
325
|
+
/** Last resort: the MCP in this process. */
|
|
295
326
|
async function serveInProcess() {
|
|
296
|
-
const { server, client } = await createMcpServer({ wsUrl: WS_URL,
|
|
327
|
+
const { server, client } = await createMcpServer({ wsUrl: WS_URL, engine: ENGINE, wasm: WASM, log });
|
|
297
328
|
const shutdown = () => {
|
|
298
329
|
try {
|
|
299
330
|
client.close();
|
|
@@ -310,13 +341,11 @@ async function serveInProcess() {
|
|
|
310
341
|
async function main() {
|
|
311
342
|
const h = await ensureDaemon();
|
|
312
343
|
if (h) {
|
|
313
|
-
log(`using daemon ${h.version} (pid ${h.pid}, ${h.sessions} session(s)
|
|
314
|
-
if (!h.bridge)
|
|
315
|
-
log(`WARNING: the daemon has no bridge listener (${h.bridgeListen ?? 'unknown'} is taken, probably by an MCP of an older build in another tab) — in-app engines cannot attach until that process is gone; the daemon retries by itself`);
|
|
344
|
+
log(`using daemon ${h.version} (pid ${h.pid}, ${h.sessions} session(s)) at ${DAEMON_URL}; daemon log: ${h.logFile ?? defaultDaemonLogFile()}`);
|
|
316
345
|
await proxyToDaemon();
|
|
317
346
|
return;
|
|
318
347
|
}
|
|
319
|
-
log('serving in-process
|
|
348
|
+
log('serving in-process');
|
|
320
349
|
await serveInProcess();
|
|
321
350
|
}
|
|
322
351
|
const USAGE = `classcad-mcp — ClassCAD MCP server (stdio). Hosts start it without arguments.
|
|
@@ -358,13 +387,17 @@ async function stopDaemon(force) {
|
|
|
358
387
|
return 0;
|
|
359
388
|
}
|
|
360
389
|
if (!force) {
|
|
361
|
-
const res = await
|
|
390
|
+
const res = await ask('POST', '/shutdown');
|
|
391
|
+
if (!res) {
|
|
392
|
+
console.error(`daemon pid ${h.pid} did not answer`);
|
|
393
|
+
return 1;
|
|
394
|
+
}
|
|
362
395
|
if (res.status === 409) {
|
|
363
396
|
console.error(`daemon pid ${h.pid} has ${h.sessions} active session(s) — not stopped.\n` +
|
|
364
397
|
`\`classcad-mcp stop --force\` ends them: every connected tab loses its drawing and reconnects on its next tool call.`);
|
|
365
398
|
return 1;
|
|
366
399
|
}
|
|
367
|
-
if (!res
|
|
400
|
+
if (!answeredOk(res)) {
|
|
368
401
|
console.error(`daemon refused to stop: HTTP ${res.status}`);
|
|
369
402
|
return 1;
|
|
370
403
|
}
|
|
@@ -374,7 +407,7 @@ async function stopDaemon(force) {
|
|
|
374
407
|
console.error('daemon did not report its pid — cannot force it');
|
|
375
408
|
return 1;
|
|
376
409
|
}
|
|
377
|
-
// SIGTERM runs the daemon's own shutdown: sessions closed,
|
|
410
|
+
// SIGTERM runs the daemon's own shutdown: sessions closed, ports released.
|
|
378
411
|
process.kill(h.pid, 'SIGTERM');
|
|
379
412
|
}
|
|
380
413
|
if (await waitGone(h.pid, 5000)) {
|