@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/dist/config.d.ts CHANGED
@@ -34,9 +34,9 @@ export interface McpServerConfig {
34
34
  */
35
35
  programCheck?: boolean | undefined;
36
36
  /**
37
- * The folder that confines each path of the `fs.*` procedures of the host (roadmap R33). The
38
- * nested `fs.*` calls of the tools (`lib.scan`, `cue.add`) are confined too. A path outside
39
- * gives `FORBIDDEN` with the reason `outside-root`. No value: no root.
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 folder that confines each path of the fs.* procedures (default: no root)
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 folder that confines each path of the fs.* procedures (default: no root)
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 `fs.*` procedures to one folder. The host serves no remote events:
14
- * its transport carries only MCP, and `events.subscribe` is not a tool.
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 `fs.*` procedures (`MCP_FS_ROOT`), or undefined. */
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
@@ -1 +1 @@
1
- {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAEL,eAAe,EAQf,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAqC,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAErG,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,4FAA4F;IAC5F,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,6DAA6D;AAC7D,wBAAsB,kBAAkB,CAAC,MAAM,GAAE,eAA8B,GAAG,OAAO,CAAC,YAAY,CAAC,CAqCtG;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,GAAE,eAA8B,GAAG,OAAO,CAAC,YAAY,CAAC,CAyCvG"}
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 `fs.*` procedures to one folder. The host serves no remote events:
14
- * its transport carries only MCP, and `events.subscribe` is not a tool.
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, capabilityRefusal, narrowCapability, surfaceRule, hostPolicy, procedureServerOptions, } from "@procedures/client";
17
- import { McpServerTransport, mergeSurfaces } from "@procedures/mcp-server";
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 fs.* procedure of the registry confines its paths, so the
56
- // nested fs calls of the tools are confined too. It adds no procedure: the tools stay the same.
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 { confineFsProcedures } = await import("@procedures/fs/module");
60
- fsRoot = confineFsProcedures(registry, config.fsRoot).root;
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
- const readOnly = config.readOnly === true;
67
- const capability = readOnly ? narrowCapability(undefined, { effects: { writes: [] } }) : undefined;
68
- const surfaceExpose = surface ? surfaceRule(surface) : undefined;
69
- const expose = capability
70
- ? (path) => {
71
- if (surfaceExpose && !surfaceExpose(path))
72
- return false;
73
- const procedure = registry.get(path);
74
- return procedure !== undefined && capabilityRefusal(procedure, capability) === undefined;
75
- }
76
- : surface;
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
- const server = new ProcedureServer(procedureServerOptions(policy, registry));
89
- return { registry, surface, policy, server, ...(fsRoot !== undefined ? { fsRoot } : {}) };
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 or a
98
- // dependency) would corrupt the protocol. The MCP SDK writes its frames with
99
- // process.stdout.write, so it is not affected.
100
- console.log = (...args) => {
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
- const expose = host.policy.expose;
108
- const transport = new McpServerTransport(host.server, {
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 ${transport.getState().toolCount} tools`);
120
+ console.error(`[mcp-server] Started with ${face?.toolCount ?? 0} tools`);
130
121
  }
131
122
  if (host.fsRoot !== undefined)
132
- console.error(`[mcp-server] fs.* refuses each path outside ${host.fsRoot}`);
133
- const url = transport.getState().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":"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"}
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
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
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().catch((error) => {
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
  });
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAEhD,mBAAmB,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACpC,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;IAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
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.7.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.7.0",
57
- "@procedures/client": "0.7.0",
58
- "@procedures/compiler": "0.7.0",
59
- "@procedures/fs": "0.7.0",
60
- "@procedures/mcp-server": "0.7.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 folder that confines each path of the `fs.*` procedures of the host (roadmap R33). The
35
- * nested `fs.*` calls of the tools (`lib.scan`, `cue.add`) are confined too. A path outside
36
- * gives `FORBIDDEN` with the reason `outside-root`. No value: no root.
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 folder that confines each path of the fs.* procedures (default: no root)
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 `fs.*` procedures to one folder. The host serves no remote events:
14
- * its transport carries only MCP, and `events.subscribe` is not a tool.
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 { McpServerTransport, mergeSurfaces, type BundleSurfaceModule } from "@procedures/mcp-server";
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 `fs.*` procedures (`MCP_FS_ROOT`), or undefined. */
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 fs.* procedure of the registry confines its paths, so the
98
- // nested fs calls of the tools are confined too. It adds no procedure: the tools stay the same.
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 { confineFsProcedures } = await import("@procedures/fs/module");
102
- fsRoot = confineFsProcedures(registry, config.fsRoot).root;
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
- const readOnly = config.readOnly === true;
109
- const capability = readOnly ? narrowCapability(undefined, { effects: { writes: [] } }) : undefined;
110
- const surfaceExpose = surface ? surfaceRule(surface) : undefined;
111
- const expose = capability
112
- ? (path: string[]): boolean => {
113
- if (surfaceExpose && !surfaceExpose(path)) return false;
114
- const procedure = registry.get(path);
115
- return procedure !== undefined && capabilityRefusal(procedure, capability) === undefined;
116
- }
117
- : surface;
118
- // The check before the run (roadmap R22): a program with an error does not start, so a refusal
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
- const server = new ProcedureServer(procedureServerOptions(policy, registry));
130
- return { registry, surface, policy, server, ...(fsRoot !== undefined ? { fsRoot } : {}) };
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 or a
140
- // dependency) would corrupt the protocol. The MCP SDK writes its frames with
141
- // process.stdout.write, so it is not affected.
142
- console.log = (...args: unknown[]): void => {
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
- const expose = host.policy.expose;
151
- const transport = new McpServerTransport(host.server, {
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 ${transport.getState().toolCount} tools`);
171
+ console.error(`[mcp-server] Started with ${face?.toolCount ?? 0} tools`);
174
172
  }
175
- if (host.fsRoot !== undefined) console.error(`[mcp-server] fs.* refuses each path outside ${host.fsRoot}`);
176
- const url = transport.getState().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().catch((error) => {
12
- console.error("[mcp-server] Fatal error:", error);
13
- process.exit(1);
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
+ });