@procedures/mcp-dev-tools 0.7.0 → 0.8.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/CHANGELOG.md +49 -0
- package/README.md +23 -13
- package/dist/.tsbuildinfo +1 -1
- package/dist/config.d.ts +4 -4
- package/dist/config.js +1 -1
- package/dist/host.d.ts +13 -6
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +55 -64
- package/dist/host.js.map +1 -1
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +9 -1
- package/dist/server.js.map +1 -1
- package/package.json +6 -6
- package/src/config.ts +4 -4
- package/src/host.ts +66 -68
- package/src/server.ts +12 -4
package/dist/config.d.ts
CHANGED
|
@@ -34,9 +34,9 @@ export interface McpServerConfig {
|
|
|
34
34
|
*/
|
|
35
35
|
programCheck?: boolean | undefined;
|
|
36
36
|
/**
|
|
37
|
-
* The
|
|
38
|
-
* nested
|
|
39
|
-
*
|
|
37
|
+
* The root of the files of the host (roadmap R33). Each procedure keeps its paths in it, also
|
|
38
|
+
* in the nested calls (`lib.scan`, `cue.add`), or the root refuses it. A path outside, and a
|
|
39
|
+
* refused tool, give `FORBIDDEN` with the reason `outside-root`. No value: no root.
|
|
40
40
|
*/
|
|
41
41
|
fsRoot?: string | undefined;
|
|
42
42
|
}
|
|
@@ -52,7 +52,7 @@ export interface McpServerConfig {
|
|
|
52
52
|
* - MCP_READ_ONLY: "true" or "1" gives only the tools that change nothing (default: false)
|
|
53
53
|
* - MCP_HTTP_PORT, MCP_HTTP_HOST, MCP_HTTP_TOKEN: serve over Streamable HTTP, not stdio
|
|
54
54
|
* - MCP_PROGRAM_CHECK: "false" or "0" starts a program without the check before the run
|
|
55
|
-
* - MCP_FS_ROOT: the
|
|
55
|
+
* - MCP_FS_ROOT: the root of the files: each tool keeps its paths in it, or it is refused (default: no root)
|
|
56
56
|
*/
|
|
57
57
|
/** The default time limit of a tool call: a build or a test run can take minutes. */
|
|
58
58
|
export declare const DEFAULT_TOOL_TIMEOUT_MS = 600000;
|
package/dist/config.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - MCP_READ_ONLY: "true" or "1" gives only the tools that change nothing (default: false)
|
|
16
16
|
* - MCP_HTTP_PORT, MCP_HTTP_HOST, MCP_HTTP_TOKEN: serve over Streamable HTTP, not stdio
|
|
17
17
|
* - MCP_PROGRAM_CHECK: "false" or "0" starts a program without the check before the run
|
|
18
|
-
* - MCP_FS_ROOT: the
|
|
18
|
+
* - MCP_FS_ROOT: the root of the files: each tool keeps its paths in it, or it is refused (default: no root)
|
|
19
19
|
*/
|
|
20
20
|
/** The default time limit of a tool call: a build or a test run can take minutes. */
|
|
21
21
|
export const DEFAULT_TOOL_TIMEOUT_MS = 600_000;
|
package/dist/host.d.ts
CHANGED
|
@@ -8,13 +8,15 @@
|
|
|
8
8
|
* - each tool call has a time limit.
|
|
9
9
|
*
|
|
10
10
|
* `server.ts` (the file in the MCP configuration of Claude Code) and `mark mcp` both start the
|
|
11
|
-
* server with `startDevToolsServer()`.
|
|
11
|
+
* server with `startDevToolsServer()`. The host comes from `createMcpHost()` of
|
|
12
|
+
* `@procedures/mcp-server` (CLI-10): the same factory as `mcp.serve` and the hosts of the apps.
|
|
12
13
|
*
|
|
13
|
-
* `MCP_FS_ROOT` confines the
|
|
14
|
-
* its
|
|
14
|
+
* `MCP_FS_ROOT` confines the procedures to one folder (`confineToRoot()` of `@procedures/fs`):
|
|
15
|
+
* each tool keeps its paths in it, or the root refuses the tool. The host serves no remote
|
|
16
|
+
* events: its transport carries only MCP, and `events.subscribe` is not a tool.
|
|
15
17
|
*/
|
|
16
18
|
import { ProcedureServer, type HostPolicy, type ProcedureModule, type ProcedureRegistry, type ToolSurface } from "@procedures/client";
|
|
17
|
-
import { type BundleSurfaceModule } from "@procedures/mcp-server";
|
|
19
|
+
import { type BundleSurfaceModule, type McpHost } from "@procedures/mcp-server";
|
|
18
20
|
import { type McpServerConfig } from "./config.js";
|
|
19
21
|
/** The modules and the tool lists of some bundles. */
|
|
20
22
|
export interface LoadedBundles {
|
|
@@ -34,15 +36,20 @@ export interface DevToolsHost {
|
|
|
34
36
|
surface: ToolSurface | undefined;
|
|
35
37
|
policy: HostPolicy;
|
|
36
38
|
server: ProcedureServer;
|
|
37
|
-
/** The absolute path of the root of the
|
|
39
|
+
/** The absolute path of the root of the files (`MCP_FS_ROOT`), or undefined. */
|
|
38
40
|
fsRoot?: string | undefined;
|
|
41
|
+
/** With a root: the keys of the procedures that the root refuses. Some of them are tools. */
|
|
42
|
+
refusedUnderRoot?: string[] | undefined;
|
|
43
|
+
/** The MCP host: `mcp.start()` starts its face (stdio, or Streamable HTTP with `config.http`). */
|
|
44
|
+
mcp: McpHost;
|
|
39
45
|
}
|
|
40
46
|
/** This function builds the host. It starts no transport. */
|
|
41
47
|
export declare function createDevToolsHost(config?: McpServerConfig): Promise<DevToolsHost>;
|
|
42
48
|
/**
|
|
43
49
|
* This function starts the dev-tools MCP server over stdio, or over Streamable HTTP with
|
|
44
50
|
* `config.http` (roadmap R26). Over stdio, stdout carries the protocol, so `console.log` goes to
|
|
45
|
-
* stderr from here on (BUGS-2026-07 Bug5).
|
|
51
|
+
* stderr from here on (BUGS-2026-07 Bug5). The stdio server stops when the client closes its
|
|
52
|
+
* input: `host.mcp.closed` then resolves.
|
|
46
53
|
*/
|
|
47
54
|
export declare function startDevToolsServer(config?: McpServerConfig): Promise<DevToolsHost>;
|
|
48
55
|
//# sourceMappingURL=host.d.ts.map
|
package/dist/host.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAEL,eAAe,EAGf,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,OAAO,EAEb,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAQ/D,sDAAsD;AACtD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC;;;OAGG;IACH,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,kFAAkF;AAClF,wBAAsB,WAAW,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC,CAgBpF;AAaD,kFAAkF;AAClF,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,eAAe,CAAC;IACxB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,6FAA6F;IAC7F,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACxC,kGAAkG;IAClG,GAAG,EAAE,OAAO,CAAC;CACd;AAcD,6DAA6D;AAC7D,wBAAsB,kBAAkB,CAAC,MAAM,GAAE,eAA8B,GAAG,OAAO,CAAC,YAAY,CAAC,CAmCtG;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,GAAE,eAA8B,GAAG,OAAO,CAAC,YAAY,CAAC,CAoBvG"}
|
package/dist/host.js
CHANGED
|
@@ -8,13 +8,15 @@
|
|
|
8
8
|
* - each tool call has a time limit.
|
|
9
9
|
*
|
|
10
10
|
* `server.ts` (the file in the MCP configuration of Claude Code) and `mark mcp` both start the
|
|
11
|
-
* server with `startDevToolsServer()`.
|
|
11
|
+
* server with `startDevToolsServer()`. The host comes from `createMcpHost()` of
|
|
12
|
+
* `@procedures/mcp-server` (CLI-10): the same factory as `mcp.serve` and the hosts of the apps.
|
|
12
13
|
*
|
|
13
|
-
* `MCP_FS_ROOT` confines the
|
|
14
|
-
* its
|
|
14
|
+
* `MCP_FS_ROOT` confines the procedures to one folder (`confineToRoot()` of `@procedures/fs`):
|
|
15
|
+
* each tool keeps its paths in it, or the root refuses the tool. The host serves no remote
|
|
16
|
+
* events: its transport carries only MCP, and `events.subscribe` is not a tool.
|
|
15
17
|
*/
|
|
16
|
-
import { PROCEDURE_REGISTRY, ProcedureServer, createRegistry, registerClientProcedures,
|
|
17
|
-
import {
|
|
18
|
+
import { PROCEDURE_REGISTRY, ProcedureServer, createRegistry, registerClientProcedures, } from "@procedures/client";
|
|
19
|
+
import { createMcpHost, mergeSurfaces, redirectConsoleToStderr, } from "@procedures/mcp-server";
|
|
18
20
|
import { programCheckInterceptor } from "@procedures/compiler";
|
|
19
21
|
import { loadConfig } from "./config.js";
|
|
20
22
|
import { ms } from "@procedures/client";
|
|
@@ -48,89 +50,78 @@ async function loadBundlesGlobally(bundles) {
|
|
|
48
50
|
}
|
|
49
51
|
return { modules: [], surfaces, global: true };
|
|
50
52
|
}
|
|
53
|
+
/** The face of the configuration: Streamable HTTP with `config.http`, else stdio. */
|
|
54
|
+
function facesOf(config) {
|
|
55
|
+
if (!config.http)
|
|
56
|
+
return { stdio: true };
|
|
57
|
+
return {
|
|
58
|
+
http: {
|
|
59
|
+
port: config.http.port,
|
|
60
|
+
...(config.http.host ? { host: config.http.host } : {}),
|
|
61
|
+
...(config.http.token ? { token: config.http.token } : {}),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
51
65
|
/** This function builds the host. It starts no transport. */
|
|
52
66
|
export async function createDevToolsHost(config = loadConfig()) {
|
|
53
67
|
const loaded = await loadBundles(config.bundles);
|
|
54
68
|
const registry = loaded.global ? PROCEDURE_REGISTRY : createRegistry(...loaded.modules);
|
|
55
|
-
// MCP_FS_ROOT (roadmap R33): each
|
|
56
|
-
// nested
|
|
69
|
+
// MCP_FS_ROOT (roadmap R33): each procedure of the registry keeps its paths in the root, also
|
|
70
|
+
// in the nested calls, or the root refuses it. It adds no procedure: the tools stay the same.
|
|
57
71
|
let fsRoot;
|
|
72
|
+
let refusedUnderRoot;
|
|
58
73
|
if (config.fsRoot !== undefined) {
|
|
59
|
-
const {
|
|
60
|
-
fsRoot =
|
|
74
|
+
const { confineToRoot } = await import("@procedures/fs/module");
|
|
75
|
+
({ root: fsRoot, refused: refusedUnderRoot } = confineToRoot(registry, config.fsRoot));
|
|
61
76
|
}
|
|
62
77
|
// Bundles without mcpNamespaces say nothing about their tools: then every procedure is a tool
|
|
63
78
|
const surface = mergeSurfaces(loaded.surfaces);
|
|
64
79
|
// Read only: the tools are the procedures that the capability permits, so the list shows only
|
|
65
|
-
// what a call can start (CAPABILITIES-2026-10)
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
:
|
|
77
|
-
// The check before the run (roadmap R22): a program with an error does not start, so a refusal
|
|
78
|
-
// in step 3 comes before the write of step 1
|
|
79
|
-
const exposeRule = typeof expose === "function" ? expose : expose ? surfaceRule(expose) : undefined;
|
|
80
|
-
const programCheck = config.programCheck === false ? [] : [programCheckInterceptor({ registry, expose: exposeRule })];
|
|
81
|
-
const policy = hostPolicy({
|
|
82
|
-
...(expose ? { expose } : {}),
|
|
83
|
-
strictCalls: true,
|
|
84
|
-
timeout: ms(config.toolTimeoutMs),
|
|
85
|
-
...(readOnly ? { readOnly: true } : {}),
|
|
86
|
-
...(programCheck.length > 0 ? { interceptors: programCheck } : {}),
|
|
80
|
+
// what a call can start (CAPABILITIES-2026-10). The check before the run (roadmap R22): a
|
|
81
|
+
// program with an error does not start, so a refusal in step 3 comes before the write of step 1.
|
|
82
|
+
const mcp = createMcpHost({
|
|
83
|
+
registry,
|
|
84
|
+
...(surface ? { surface } : {}),
|
|
85
|
+
readOnly: config.readOnly === true,
|
|
86
|
+
policy: { strictCalls: true, timeout: ms(config.toolTimeoutMs) },
|
|
87
|
+
...(config.programCheck === false ? {} : { interceptors: ({ expose }) => [programCheckInterceptor({ registry, expose })] }),
|
|
88
|
+
name: config.serverName,
|
|
89
|
+
version: config.serverVersion,
|
|
90
|
+
debug: config.debug,
|
|
91
|
+
faces: facesOf(config),
|
|
87
92
|
});
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
return {
|
|
94
|
+
registry,
|
|
95
|
+
surface,
|
|
96
|
+
policy: mcp.policy,
|
|
97
|
+
server: mcp.server,
|
|
98
|
+
mcp,
|
|
99
|
+
...(fsRoot !== undefined ? { fsRoot, refusedUnderRoot } : {}),
|
|
100
|
+
};
|
|
90
101
|
}
|
|
91
102
|
/**
|
|
92
103
|
* This function starts the dev-tools MCP server over stdio, or over Streamable HTTP with
|
|
93
104
|
* `config.http` (roadmap R26). Over stdio, stdout carries the protocol, so `console.log` goes to
|
|
94
|
-
* stderr from here on (BUGS-2026-07 Bug5).
|
|
105
|
+
* stderr from here on (BUGS-2026-07 Bug5). The stdio server stops when the client closes its
|
|
106
|
+
* input: `host.mcp.closed` then resolves.
|
|
95
107
|
*/
|
|
96
108
|
export async function startDevToolsServer(config = loadConfig()) {
|
|
97
|
-
// Defense-in-depth for the stdio transport: a stray console.log (from a procedure handler
|
|
98
|
-
// dependency) would corrupt the protocol. The MCP SDK writes its frames
|
|
99
|
-
// process.stdout.write, so it is not affected.
|
|
100
|
-
|
|
101
|
-
console.error(...args);
|
|
102
|
-
};
|
|
109
|
+
// Defense-in-depth for the stdio transport: a stray console.log (from a procedure handler, a
|
|
110
|
+
// dependency or a bundle that loads) would corrupt the protocol. The MCP SDK writes its frames
|
|
111
|
+
// with process.stdout.write, so it is not affected.
|
|
112
|
+
redirectConsoleToStderr();
|
|
103
113
|
if (config.debug) {
|
|
104
114
|
console.error("[mcp-server] Starting with config:", JSON.stringify(config, null, 2));
|
|
105
115
|
}
|
|
106
116
|
const host = await createDevToolsHost(config);
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
...(config.http
|
|
110
|
-
? {
|
|
111
|
-
transport: "http",
|
|
112
|
-
httpOptions: {
|
|
113
|
-
port: config.http.port,
|
|
114
|
-
...(config.http.host ? { host: config.http.host } : {}),
|
|
115
|
-
...(config.http.token ? { token: config.http.token } : {}),
|
|
116
|
-
},
|
|
117
|
-
}
|
|
118
|
-
: { transport: "stdio" }),
|
|
119
|
-
serverInfo: {
|
|
120
|
-
name: config.serverName,
|
|
121
|
-
version: config.serverVersion,
|
|
122
|
-
},
|
|
123
|
-
debug: config.debug,
|
|
124
|
-
...(expose ? { toolFilter: { include: expose } } : {}),
|
|
125
|
-
});
|
|
126
|
-
host.server.addTransport(transport);
|
|
127
|
-
await host.server.start();
|
|
117
|
+
await host.mcp.start();
|
|
118
|
+
const face = host.mcp.state().faces[0];
|
|
128
119
|
if (config.debug) {
|
|
129
|
-
console.error(`[mcp-server] Started with ${
|
|
120
|
+
console.error(`[mcp-server] Started with ${face?.toolCount ?? 0} tools`);
|
|
130
121
|
}
|
|
131
122
|
if (host.fsRoot !== undefined)
|
|
132
|
-
console.error(`[mcp-server]
|
|
133
|
-
const url =
|
|
123
|
+
console.error(`[mcp-server] Each tool refuses a path outside ${host.fsRoot}. Refused under the root: ${String(host.refusedUnderRoot)}`);
|
|
124
|
+
const url = face?.url;
|
|
134
125
|
if (url)
|
|
135
126
|
console.error(`[mcp-server] Streamable HTTP at ${url}${config.http?.token ? " (send Authorization: Bearer <token>)" : ""}`);
|
|
136
127
|
return host;
|
package/dist/host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Starts an MCP server that exposes procedures as tools to Claude Code and Claude Desktop. The
|
|
5
5
|
* MCP configuration of a client runs this file (`node packages/impl-mcp-dev/dist/server.js`).
|
|
6
6
|
* Configurable via environment variables (see `config.ts`). `mark mcp` starts the same server.
|
|
7
|
+
*
|
|
8
|
+
* When the client closes the input of stdio, the host stops (the calls in flight get their grace
|
|
9
|
+
* time), and this process ends (CLI-10).
|
|
7
10
|
*/
|
|
8
11
|
export {};
|
|
9
12
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
package/dist/server.js
CHANGED
|
@@ -4,9 +4,17 @@
|
|
|
4
4
|
* Starts an MCP server that exposes procedures as tools to Claude Code and Claude Desktop. The
|
|
5
5
|
* MCP configuration of a client runs this file (`node packages/impl-mcp-dev/dist/server.js`).
|
|
6
6
|
* Configurable via environment variables (see `config.ts`). `mark mcp` starts the same server.
|
|
7
|
+
*
|
|
8
|
+
* When the client closes the input of stdio, the host stops (the calls in flight get their grace
|
|
9
|
+
* time), and this process ends (CLI-10).
|
|
7
10
|
*/
|
|
8
11
|
import { startDevToolsServer } from "./host.js";
|
|
9
|
-
startDevToolsServer()
|
|
12
|
+
startDevToolsServer()
|
|
13
|
+
.then(async (host) => {
|
|
14
|
+
await host.mcp.closed;
|
|
15
|
+
process.exit(0);
|
|
16
|
+
})
|
|
17
|
+
.catch((error) => {
|
|
10
18
|
console.error("[mcp-server] Fatal error:", error);
|
|
11
19
|
process.exit(1);
|
|
12
20
|
});
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAEhD,mBAAmB,EAAE;KAClB,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;IACnB,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;IACtB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC;KACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACf,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;IAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@procedures/mcp-dev-tools",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.1",
|
|
5
5
|
"description": "A ready-to-use MCP server for Claude Code and Claude Desktop: 91 developer tools (docker, mongo, sqlite, cue, vitest, workspace) with a strict call policy.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mcp",
|
|
@@ -53,11 +53,11 @@
|
|
|
53
53
|
"provenance": true
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@procedures/bundle-mcp": "0.
|
|
57
|
-
"@procedures/client": "0.
|
|
58
|
-
"@procedures/compiler": "0.
|
|
59
|
-
"@procedures/fs": "0.
|
|
60
|
-
"@procedures/mcp-server": "0.
|
|
56
|
+
"@procedures/bundle-mcp": "0.8.1",
|
|
57
|
+
"@procedures/client": "0.8.1",
|
|
58
|
+
"@procedures/compiler": "0.8.1",
|
|
59
|
+
"@procedures/fs": "0.8.1",
|
|
60
|
+
"@procedures/mcp-server": "0.8.1"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@mark1russell7/cue": "^1.0.0",
|
package/src/config.ts
CHANGED
|
@@ -31,9 +31,9 @@ export interface McpServerConfig {
|
|
|
31
31
|
*/
|
|
32
32
|
programCheck?: boolean | undefined;
|
|
33
33
|
/**
|
|
34
|
-
* The
|
|
35
|
-
* nested
|
|
36
|
-
*
|
|
34
|
+
* The root of the files of the host (roadmap R33). Each procedure keeps its paths in it, also
|
|
35
|
+
* in the nested calls (`lib.scan`, `cue.add`), or the root refuses it. A path outside, and a
|
|
36
|
+
* refused tool, give `FORBIDDEN` with the reason `outside-root`. No value: no root.
|
|
37
37
|
*/
|
|
38
38
|
fsRoot?: string | undefined;
|
|
39
39
|
}
|
|
@@ -50,7 +50,7 @@ export interface McpServerConfig {
|
|
|
50
50
|
* - MCP_READ_ONLY: "true" or "1" gives only the tools that change nothing (default: false)
|
|
51
51
|
* - MCP_HTTP_PORT, MCP_HTTP_HOST, MCP_HTTP_TOKEN: serve over Streamable HTTP, not stdio
|
|
52
52
|
* - MCP_PROGRAM_CHECK: "false" or "0" starts a program without the check before the run
|
|
53
|
-
* - MCP_FS_ROOT: the
|
|
53
|
+
* - MCP_FS_ROOT: the root of the files: each tool keeps its paths in it, or it is refused (default: no root)
|
|
54
54
|
*/
|
|
55
55
|
/** The default time limit of a tool call: a build or a test run can take minutes. */
|
|
56
56
|
export const DEFAULT_TOOL_TIMEOUT_MS = 600_000;
|
package/src/host.ts
CHANGED
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
* - each tool call has a time limit.
|
|
9
9
|
*
|
|
10
10
|
* `server.ts` (the file in the MCP configuration of Claude Code) and `mark mcp` both start the
|
|
11
|
-
* server with `startDevToolsServer()`.
|
|
11
|
+
* server with `startDevToolsServer()`. The host comes from `createMcpHost()` of
|
|
12
|
+
* `@procedures/mcp-server` (CLI-10): the same factory as `mcp.serve` and the hosts of the apps.
|
|
12
13
|
*
|
|
13
|
-
* `MCP_FS_ROOT` confines the
|
|
14
|
-
* its
|
|
14
|
+
* `MCP_FS_ROOT` confines the procedures to one folder (`confineToRoot()` of `@procedures/fs`):
|
|
15
|
+
* each tool keeps its paths in it, or the root refuses the tool. The host serves no remote
|
|
16
|
+
* events: its transport carries only MCP, and `events.subscribe` is not a tool.
|
|
15
17
|
*/
|
|
16
18
|
|
|
17
19
|
import {
|
|
@@ -19,17 +21,19 @@ import {
|
|
|
19
21
|
ProcedureServer,
|
|
20
22
|
createRegistry,
|
|
21
23
|
registerClientProcedures,
|
|
22
|
-
capabilityRefusal,
|
|
23
|
-
narrowCapability,
|
|
24
|
-
surfaceRule,
|
|
25
|
-
hostPolicy,
|
|
26
|
-
procedureServerOptions,
|
|
27
24
|
type HostPolicy,
|
|
28
25
|
type ProcedureModule,
|
|
29
26
|
type ProcedureRegistry,
|
|
30
27
|
type ToolSurface,
|
|
31
28
|
} from "@procedures/client";
|
|
32
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
createMcpHost,
|
|
31
|
+
mergeSurfaces,
|
|
32
|
+
redirectConsoleToStderr,
|
|
33
|
+
type BundleSurfaceModule,
|
|
34
|
+
type McpHost,
|
|
35
|
+
type McpHostFaces,
|
|
36
|
+
} from "@procedures/mcp-server";
|
|
33
37
|
import { programCheckInterceptor } from "@procedures/compiler";
|
|
34
38
|
import { loadConfig, type McpServerConfig } from "./config.js";
|
|
35
39
|
import { ms } from "@procedures/client";
|
|
@@ -86,94 +90,88 @@ export interface DevToolsHost {
|
|
|
86
90
|
surface: ToolSurface | undefined;
|
|
87
91
|
policy: HostPolicy;
|
|
88
92
|
server: ProcedureServer;
|
|
89
|
-
/** The absolute path of the root of the
|
|
93
|
+
/** The absolute path of the root of the files (`MCP_FS_ROOT`), or undefined. */
|
|
90
94
|
fsRoot?: string | undefined;
|
|
95
|
+
/** With a root: the keys of the procedures that the root refuses. Some of them are tools. */
|
|
96
|
+
refusedUnderRoot?: string[] | undefined;
|
|
97
|
+
/** The MCP host: `mcp.start()` starts its face (stdio, or Streamable HTTP with `config.http`). */
|
|
98
|
+
mcp: McpHost;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The face of the configuration: Streamable HTTP with `config.http`, else stdio. */
|
|
102
|
+
function facesOf(config: McpServerConfig): McpHostFaces {
|
|
103
|
+
if (!config.http) return { stdio: true };
|
|
104
|
+
return {
|
|
105
|
+
http: {
|
|
106
|
+
port: config.http.port,
|
|
107
|
+
...(config.http.host ? { host: config.http.host } : {}),
|
|
108
|
+
...(config.http.token ? { token: config.http.token } : {}),
|
|
109
|
+
},
|
|
110
|
+
};
|
|
91
111
|
}
|
|
92
112
|
|
|
93
113
|
/** This function builds the host. It starts no transport. */
|
|
94
114
|
export async function createDevToolsHost(config: McpServerConfig = loadConfig()): Promise<DevToolsHost> {
|
|
95
115
|
const loaded = await loadBundles(config.bundles);
|
|
96
116
|
const registry = loaded.global ? PROCEDURE_REGISTRY : createRegistry(...loaded.modules);
|
|
97
|
-
// MCP_FS_ROOT (roadmap R33): each
|
|
98
|
-
// nested
|
|
117
|
+
// MCP_FS_ROOT (roadmap R33): each procedure of the registry keeps its paths in the root, also
|
|
118
|
+
// in the nested calls, or the root refuses it. It adds no procedure: the tools stay the same.
|
|
99
119
|
let fsRoot: string | undefined;
|
|
120
|
+
let refusedUnderRoot: string[] | undefined;
|
|
100
121
|
if (config.fsRoot !== undefined) {
|
|
101
|
-
const {
|
|
102
|
-
fsRoot =
|
|
122
|
+
const { confineToRoot } = await import("@procedures/fs/module");
|
|
123
|
+
({ root: fsRoot, refused: refusedUnderRoot } = confineToRoot(registry, config.fsRoot));
|
|
103
124
|
}
|
|
104
125
|
// Bundles without mcpNamespaces say nothing about their tools: then every procedure is a tool
|
|
105
126
|
const surface = mergeSurfaces(loaded.surfaces);
|
|
106
127
|
// Read only: the tools are the procedures that the capability permits, so the list shows only
|
|
107
|
-
// what a call can start (CAPABILITIES-2026-10)
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
:
|
|
118
|
-
|
|
119
|
-
// in step 3 comes before the write of step 1
|
|
120
|
-
const exposeRule = typeof expose === "function" ? expose : expose ? surfaceRule(expose) : undefined;
|
|
121
|
-
const programCheck = config.programCheck === false ? [] : [programCheckInterceptor({ registry, expose: exposeRule })];
|
|
122
|
-
const policy = hostPolicy({
|
|
123
|
-
...(expose ? { expose } : {}),
|
|
124
|
-
strictCalls: true,
|
|
125
|
-
timeout: ms(config.toolTimeoutMs),
|
|
126
|
-
...(readOnly ? { readOnly: true } : {}),
|
|
127
|
-
...(programCheck.length > 0 ? { interceptors: programCheck } : {}),
|
|
128
|
+
// what a call can start (CAPABILITIES-2026-10). The check before the run (roadmap R22): a
|
|
129
|
+
// program with an error does not start, so a refusal in step 3 comes before the write of step 1.
|
|
130
|
+
const mcp = createMcpHost({
|
|
131
|
+
registry,
|
|
132
|
+
...(surface ? { surface } : {}),
|
|
133
|
+
readOnly: config.readOnly === true,
|
|
134
|
+
policy: { strictCalls: true, timeout: ms(config.toolTimeoutMs) },
|
|
135
|
+
...(config.programCheck === false ? {} : { interceptors: ({ expose }) => [programCheckInterceptor({ registry, expose })] }),
|
|
136
|
+
name: config.serverName,
|
|
137
|
+
version: config.serverVersion,
|
|
138
|
+
debug: config.debug,
|
|
139
|
+
faces: facesOf(config),
|
|
128
140
|
});
|
|
129
|
-
|
|
130
|
-
|
|
141
|
+
return {
|
|
142
|
+
registry,
|
|
143
|
+
surface,
|
|
144
|
+
policy: mcp.policy,
|
|
145
|
+
server: mcp.server,
|
|
146
|
+
mcp,
|
|
147
|
+
...(fsRoot !== undefined ? { fsRoot, refusedUnderRoot } : {}),
|
|
148
|
+
};
|
|
131
149
|
}
|
|
132
150
|
|
|
133
151
|
/**
|
|
134
152
|
* This function starts the dev-tools MCP server over stdio, or over Streamable HTTP with
|
|
135
153
|
* `config.http` (roadmap R26). Over stdio, stdout carries the protocol, so `console.log` goes to
|
|
136
|
-
* stderr from here on (BUGS-2026-07 Bug5).
|
|
154
|
+
* stderr from here on (BUGS-2026-07 Bug5). The stdio server stops when the client closes its
|
|
155
|
+
* input: `host.mcp.closed` then resolves.
|
|
137
156
|
*/
|
|
138
157
|
export async function startDevToolsServer(config: McpServerConfig = loadConfig()): Promise<DevToolsHost> {
|
|
139
|
-
// Defense-in-depth for the stdio transport: a stray console.log (from a procedure handler
|
|
140
|
-
// dependency) would corrupt the protocol. The MCP SDK writes its frames
|
|
141
|
-
// process.stdout.write, so it is not affected.
|
|
142
|
-
|
|
143
|
-
console.error(...args);
|
|
144
|
-
};
|
|
158
|
+
// Defense-in-depth for the stdio transport: a stray console.log (from a procedure handler, a
|
|
159
|
+
// dependency or a bundle that loads) would corrupt the protocol. The MCP SDK writes its frames
|
|
160
|
+
// with process.stdout.write, so it is not affected.
|
|
161
|
+
redirectConsoleToStderr();
|
|
145
162
|
if (config.debug) {
|
|
146
163
|
console.error("[mcp-server] Starting with config:", JSON.stringify(config, null, 2));
|
|
147
164
|
}
|
|
148
165
|
|
|
149
166
|
const host = await createDevToolsHost(config);
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
...(config.http
|
|
153
|
-
? {
|
|
154
|
-
transport: "http" as const,
|
|
155
|
-
httpOptions: {
|
|
156
|
-
port: config.http.port,
|
|
157
|
-
...(config.http.host ? { host: config.http.host } : {}),
|
|
158
|
-
...(config.http.token ? { token: config.http.token } : {}),
|
|
159
|
-
},
|
|
160
|
-
}
|
|
161
|
-
: { transport: "stdio" as const }),
|
|
162
|
-
serverInfo: {
|
|
163
|
-
name: config.serverName,
|
|
164
|
-
version: config.serverVersion,
|
|
165
|
-
},
|
|
166
|
-
debug: config.debug,
|
|
167
|
-
...(expose ? { toolFilter: { include: expose } } : {}),
|
|
168
|
-
});
|
|
169
|
-
host.server.addTransport(transport);
|
|
170
|
-
await host.server.start();
|
|
167
|
+
await host.mcp.start();
|
|
168
|
+
const face = host.mcp.state().faces[0];
|
|
171
169
|
|
|
172
170
|
if (config.debug) {
|
|
173
|
-
console.error(`[mcp-server] Started with ${
|
|
171
|
+
console.error(`[mcp-server] Started with ${face?.toolCount ?? 0} tools`);
|
|
174
172
|
}
|
|
175
|
-
if (host.fsRoot !== undefined) console.error(`[mcp-server]
|
|
176
|
-
const url =
|
|
173
|
+
if (host.fsRoot !== undefined) console.error(`[mcp-server] Each tool refuses a path outside ${host.fsRoot}. Refused under the root: ${String(host.refusedUnderRoot)}`);
|
|
174
|
+
const url = face?.url;
|
|
177
175
|
if (url) console.error(`[mcp-server] Streamable HTTP at ${url}${config.http?.token ? " (send Authorization: Bearer <token>)" : ""}`);
|
|
178
176
|
return host;
|
|
179
177
|
}
|
package/src/server.ts
CHANGED
|
@@ -4,11 +4,19 @@
|
|
|
4
4
|
* Starts an MCP server that exposes procedures as tools to Claude Code and Claude Desktop. The
|
|
5
5
|
* MCP configuration of a client runs this file (`node packages/impl-mcp-dev/dist/server.js`).
|
|
6
6
|
* Configurable via environment variables (see `config.ts`). `mark mcp` starts the same server.
|
|
7
|
+
*
|
|
8
|
+
* When the client closes the input of stdio, the host stops (the calls in flight get their grace
|
|
9
|
+
* time), and this process ends (CLI-10).
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
12
|
import { startDevToolsServer } from "./host.js";
|
|
10
13
|
|
|
11
|
-
startDevToolsServer()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
startDevToolsServer()
|
|
15
|
+
.then(async (host) => {
|
|
16
|
+
await host.mcp.closed;
|
|
17
|
+
process.exit(0);
|
|
18
|
+
})
|
|
19
|
+
.catch((error) => {
|
|
20
|
+
console.error("[mcp-server] Fatal error:", error);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
});
|