@remcp/runtime 0.2.122 → 0.2.123

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.123 — 2026-10-01
4
+
5
+ - The compact local MCP facade now tracks live capability changes after startup. It advertises `tools.listChanged`, refreshes grouped input/output schemas, and adds or removes domains such as `control_browser` when loopback CDP becomes available or unavailable.
6
+ - Compact and granular capability polling now serializes discovery passes so a slow probe cannot overlap the next interval and apply a stale capability result out of order.
7
+ - The underlying 83 granular runtime tool names, input/output schemas, annotations, and handlers remain unchanged from 0.2.122.
8
+
3
9
  ## 0.2.122 — 2026-09-30
4
10
 
5
11
  - No runtime tool names, schemas, or device-side implementations change in this release. The runtime package version remains in lockstep with the Windows bootstrap release, which pins the private Windows Node execution engine for reproducible installs.
package/README.md CHANGED
@@ -27,7 +27,7 @@ npx @remcp/runtime # granular capability-aware MCP server over stdi
27
27
  node ./src/compact.mjs --print-tools # compact local façade for scanners/local MCP hosts
28
28
  ```
29
29
 
30
- `src/compact.mjs` is a second stdio entrypoint over the **same real handlers**. It advertises `read_file` plus up to seven `verb_noun` domain tools (`manage_files`, `run_terminal`, `control_computer`, `view_image`, `control_browser`, `manage_system`, `manage_documents`) and narrows each operation enum to capabilities available on the current host. It exists for directory scanners and local MCP hosts that benefit from a small selection surface; the paired ReMCP agent continues to use the granular capability-aware entrypoint.
30
+ `src/compact.mjs` is a second stdio entrypoint over the **same real handlers**. It advertises `read_file` plus up to seven `verb_noun` domain tools (`manage_files`, `run_terminal`, `control_computer`, `view_image`, `control_browser`, `manage_system`, `manage_documents`) and narrows each operation enum to capabilities available on the current host. It also tracks capability changes while running: grouped schemas are refreshed and `notifications/tools/list_changed` is emitted when a domain appears, disappears, or changes membership. It exists for directory scanners and local MCP hosts that benefit from a small selection surface; the paired ReMCP agent continues to use the granular capability-aware entrypoint.
31
31
 
32
32
  ## Tools
33
33
 
@@ -81,7 +81,7 @@ example: they are enabled only while the runtime can use a supported loopback br
81
81
  The generated hosted reference covers the current **10 hosted production definitions**, all model-visible
82
82
  and routing to the 83 granular runtime operations described above; the optional widget-enabled surface is 15.
83
83
  The separate compact local stdio entrypoint advertises `read_file` plus up to seven domain tools
84
- (up to 8 tools total) while the paired agent keeps using the capability-aware granular runtime.
84
+ (up to 8 tools total) and keeps those groups capability-aware at runtime with `tools/list_changed`; the paired agent keeps using the capability-aware granular runtime.
85
85
  See https://remcp.site/docs#tools and public `docs/TOOLS.md`.
86
86
 
87
87
  ## No approval staircase
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/runtime",
3
- "version": "0.2.122",
3
+ "version": "0.2.123",
4
4
  "description": "First-party ReMCP local device runtime: filesystem, terminal, computer-use, browser, diagnostics and document tools over MCP.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,14 @@
1
+ import process from 'node:process';
1
2
  import { compactRuntimeToolDefinitions, resolveCompactRuntimeCall } from './compact-catalog.mjs';
2
3
  import { invokeTool } from './invoke.mjs';
3
4
 
5
+ const DEFAULT_CAPABILITY_POLL_MS = 3000;
6
+
7
+ function pollIntervalMs() {
8
+ const requested = Number(process.env.REMCP_CAPABILITY_POLL_MS);
9
+ return Math.min(60_000, Math.max(250, Number.isFinite(requested) && requested > 0 ? requested : DEFAULT_CAPABILITY_POLL_MS));
10
+ }
11
+
4
12
  function toolConfig(definition, fromJsonSchema) {
5
13
  return {
6
14
  title:definition.title,
@@ -11,20 +19,45 @@ function toolConfig(definition, fromJsonSchema) {
11
19
  };
12
20
  }
13
21
 
22
+ function toolUpdate(definition, fromJsonSchema) {
23
+ return {
24
+ title:definition.title,
25
+ description:definition.description,
26
+ paramsSchema:fromJsonSchema(definition.inputSchema || { type:'object' }),
27
+ ...(definition.outputSchema ? { outputSchema:fromJsonSchema(definition.outputSchema) } : {}),
28
+ annotations:definition.annotations,
29
+ enabled:true,
30
+ };
31
+ }
32
+
33
+ function definitionSignature(definition) {
34
+ if (!definition) return '';
35
+ return JSON.stringify({
36
+ title:definition.title,
37
+ description:definition.description,
38
+ inputSchema:definition.inputSchema,
39
+ outputSchema:definition.outputSchema,
40
+ annotations:definition.annotations,
41
+ });
42
+ }
43
+
14
44
  export async function startCompactRuntimeMcpServer({ version, instructions, onError = () => {} } = {}) {
15
45
  const [{ McpServer, fromJsonSchema }, { serveStdio }] = await Promise.all([
16
46
  import('@modelcontextprotocol/server'),
17
47
  import('@modelcontextprotocol/server/stdio'),
18
48
  ]);
19
- const definitions = await compactRuntimeToolDefinitions();
49
+ let definitions = await compactRuntimeToolDefinitions();
50
+ const currentDefinitions = new Map(definitions.map(definition => [definition.name, definition]));
20
51
  let activeServer = null;
52
+ let capabilityTimer = null;
53
+ let capabilityPollRunning = false;
21
54
  let closed = false;
22
55
 
23
56
  const serverHandle = serveStdio(() => {
24
57
  const server = new McpServer(
25
58
  { name:'remcp-runtime-compact', version },
26
59
  {
27
- capabilities:{ tools:{} },
60
+ capabilities:{ tools:{ listChanged:true } },
28
61
  instructions,
29
62
  cacheHints:{
30
63
  'tools/list':{ ttlMs:3000, cacheScope:'private' },
@@ -33,20 +66,78 @@ export async function startCompactRuntimeMcpServer({ version, instructions, onEr
33
66
  },
34
67
  );
35
68
  activeServer = server;
36
- for (const definition of definitions) {
37
- server.registerTool(
69
+ const registrations = new Map();
70
+
71
+ const registerDefinition = definition => {
72
+ const registered = server.registerTool(
38
73
  definition.name,
39
74
  toolConfig(definition, fromJsonSchema),
40
75
  async (args, context) => {
41
76
  try {
42
- const resolved = resolveCompactRuntimeCall(definition.name, args || {}, definitions);
77
+ const activeDefinitions = [...currentDefinitions.values()];
78
+ const resolved = resolveCompactRuntimeCall(definition.name, args || {}, activeDefinitions);
43
79
  return await invokeTool(resolved.runtimeName, resolved.runtimeArguments, { signal:context?.mcpReq?.signal });
44
80
  } catch (error) {
45
81
  return { content:[{ type:'text', text:error instanceof Error ? error.message : String(error) }], isError:true };
46
82
  }
47
83
  },
48
84
  );
49
- }
85
+ registrations.set(definition.name, registered);
86
+ return registered;
87
+ };
88
+
89
+ for (const definition of definitions) registerDefinition(definition);
90
+
91
+ capabilityTimer = setInterval(async () => {
92
+ if (closed || capabilityPollRunning) return;
93
+ capabilityPollRunning = true;
94
+ try {
95
+ const nextDefinitions = await compactRuntimeToolDefinitions();
96
+ const nextByName = new Map(nextDefinitions.map(definition => [definition.name, definition]));
97
+ const names = new Set([...registrations.keys(), ...nextByName.keys()]);
98
+
99
+ for (const name of names) {
100
+ const before = currentDefinitions.get(name);
101
+ const after = nextByName.get(name);
102
+ const registered = registrations.get(name);
103
+
104
+ if (!after) {
105
+ currentDefinitions.delete(name);
106
+ if (registered?.enabled) registered.disable();
107
+ continue;
108
+ }
109
+
110
+ if (!registered) {
111
+ currentDefinitions.set(name, after);
112
+ registerDefinition(after);
113
+ continue;
114
+ }
115
+
116
+ if (definitionSignature(before) === definitionSignature(after) && registered.enabled !== false) {
117
+ currentDefinitions.set(name, after);
118
+ continue;
119
+ }
120
+
121
+ const previous = before;
122
+ currentDefinitions.set(name, after);
123
+ try {
124
+ registered.update(toolUpdate(after, fromJsonSchema));
125
+ } catch (error) {
126
+ if (previous) currentDefinitions.set(name, previous);
127
+ else currentDefinitions.delete(name);
128
+ throw error;
129
+ }
130
+ }
131
+
132
+ definitions = nextDefinitions;
133
+ } catch (error) {
134
+ onError(error instanceof Error ? error : new Error(String(error)));
135
+ } finally {
136
+ capabilityPollRunning = false;
137
+ }
138
+ }, pollIntervalMs());
139
+ capabilityTimer.unref?.();
140
+
50
141
  return server;
51
142
  }, {
52
143
  legacy:'serve',
@@ -54,7 +145,7 @@ export async function startCompactRuntimeMcpServer({ version, instructions, onEr
54
145
  });
55
146
 
56
147
  return {
57
- definitions,
148
+ get definitions() { return definitions; },
58
149
  async notification(notification) {
59
150
  if (!activeServer) return;
60
151
  await activeServer.server.notification(notification);
@@ -62,6 +153,8 @@ export async function startCompactRuntimeMcpServer({ version, instructions, onEr
62
153
  async close() {
63
154
  if (closed) return;
64
155
  closed = true;
156
+ if (capabilityTimer) clearInterval(capabilityTimer);
157
+ capabilityTimer = null;
65
158
  await serverHandle.close();
66
159
  activeServer = null;
67
160
  },
package/src/mcp-v2.mjs CHANGED
@@ -32,6 +32,7 @@ export async function startRuntimeMcpServer({ version, instructions, onError = (
32
32
  ]);
33
33
  let activeServer = null;
34
34
  let capabilityTimer = null;
35
+ let capabilityPollRunning = false;
35
36
  let closed = false;
36
37
 
37
38
  const serverHandle = serveStdio(() => {
@@ -65,7 +66,8 @@ export async function startRuntimeMcpServer({ version, instructions, onError = (
65
66
 
66
67
  let lastSupported = initiallySupported;
67
68
  capabilityTimer = setInterval(async () => {
68
- if (closed) return;
69
+ if (closed || capabilityPollRunning) return;
70
+ capabilityPollRunning = true;
69
71
  try {
70
72
  const next = new Set((await advertisedExtendedTools()).map(tool => tool.name));
71
73
  let changed = false;
@@ -87,6 +89,8 @@ export async function startRuntimeMcpServer({ version, instructions, onError = (
87
89
  if (changed) server.sendToolListChanged();
88
90
  } catch (error) {
89
91
  onError(error instanceof Error ? error : new Error(String(error)));
92
+ } finally {
93
+ capabilityPollRunning = false;
90
94
  }
91
95
  }, pollIntervalMs());
92
96
  capabilityTimer.unref?.();