@remcp/runtime 0.2.87 → 0.2.88
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 +7 -0
- package/README.md +4 -1
- package/package.json +1 -1
- package/src/compact-catalog.mjs +158 -0
- package/src/compact-mcp.mjs +69 -0
- package/src/compact.mjs +68 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.88 — 2026-09-21
|
|
4
|
+
|
|
5
|
+
- Added a real compact stdio façade over the same runtime handlers: `read_file` plus up to seven capability-filtered `verb_noun` domain tools.
|
|
6
|
+
- Grouped operations keep their original closed schemas and dispatch through `invokeTool`, so the compact surface changes discovery, not execution or permissions.
|
|
7
|
+
- The normal paired-agent runtime remains the granular 83-operation capability-aware server.
|
|
8
|
+
- Added compact-catalog parity and end-to-end stdio tests.
|
|
9
|
+
|
|
3
10
|
## 0.2.87 — 2026-09-21
|
|
4
11
|
|
|
5
12
|
- macOS `event_log` now streams only the requested head lines from `log show` and terminates the child once the limit is reached.
|
package/README.md
CHANGED
|
@@ -23,9 +23,12 @@ To run it directly:
|
|
|
23
23
|
```bash
|
|
24
24
|
npx @remcp/runtime --describe # version, limits, configuration, telemetry state
|
|
25
25
|
npx @remcp/runtime --print-tools
|
|
26
|
-
npx @remcp/runtime # MCP server over stdio
|
|
26
|
+
npx @remcp/runtime # granular capability-aware MCP server over stdio
|
|
27
|
+
node ./src/compact.mjs --print-tools # compact local façade for scanners/local MCP hosts
|
|
27
28
|
```
|
|
28
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.
|
|
31
|
+
|
|
29
32
|
## Tools
|
|
30
33
|
|
|
31
34
|
83 tools are implemented in this repository: the original 44 filesystem/search/terminal/process tools
|
package/package.json
CHANGED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { TEXT_OUTPUT_SCHEMA, toolDefinitions } from './catalog.mjs';
|
|
2
|
+
import { advertisedExtendedTools, extendedToolDefinitions } from './extended/catalog.mjs';
|
|
3
|
+
|
|
4
|
+
export const compactRuntimeGroups = Object.freeze({
|
|
5
|
+
manage_files: Object.freeze([
|
|
6
|
+
'read_files','read_multiple_files','read_binary','write_binary','hash_file','list_directory','get_file_info',
|
|
7
|
+
'write_file','write_files','apply_patch','set_permissions','edit_block','replace_lines','replace_in_files','diff_files',
|
|
8
|
+
'move_to_trash','create_directory','move_file','copy_file','copy_paths','move_paths','delete_path','delete_paths',
|
|
9
|
+
'create_archive','extract_archive','start_search','get_more_search_results','stop_search','list_searches',
|
|
10
|
+
]),
|
|
11
|
+
run_terminal: Object.freeze([
|
|
12
|
+
'start_process','read_process_output','interact_with_process','wait_for_process_output','force_terminate','list_sessions',
|
|
13
|
+
]),
|
|
14
|
+
control_computer: Object.freeze([
|
|
15
|
+
'computer_snapshot','list_windows','ui_snapshot','ui_find','wait_for_ui','display_inventory',
|
|
16
|
+
'computer_action','window_action','launch_app','ui_action','type_text','keyboard','pointer','drag_drop','scroll',
|
|
17
|
+
'clipboard','open_path','reveal_path','notification','record_screen',
|
|
18
|
+
]),
|
|
19
|
+
view_image: Object.freeze(['read_image','take_screenshot','screenshot_region']),
|
|
20
|
+
control_browser: Object.freeze([
|
|
21
|
+
'browser_tabs','browser_navigate','browser_snapshot','browser_find','browser_action','browser_wait','browser_evaluate',
|
|
22
|
+
]),
|
|
23
|
+
manage_system: Object.freeze([
|
|
24
|
+
'get_system_info','list_processes','kill_process','get_runtime_info','get_runtime_stats','set_config_value',
|
|
25
|
+
'service','event_log','network','installed_apps','environment','audio','power_action',
|
|
26
|
+
]),
|
|
27
|
+
manage_documents: Object.freeze(['read_document','edit_spreadsheet','edit_document','pdf_action']),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const groupTitles = Object.freeze({
|
|
31
|
+
manage_files:'Manage files',
|
|
32
|
+
run_terminal:'Run terminal operations',
|
|
33
|
+
control_computer:'Control computer',
|
|
34
|
+
view_image:'View image or screenshot',
|
|
35
|
+
control_browser:'Control browser',
|
|
36
|
+
manage_system:'Manage system',
|
|
37
|
+
manage_documents:'Manage documents',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const groupDescriptions = Object.freeze({
|
|
41
|
+
manage_files:'Use this when the task is about files, directories, archives, or filesystem search on this computer. Choose exactly one operation; parameters must contain only that operation’s arguments and are validated against its closed schema. Write, edit, move, permission, and delete operations can change or remove local data, while read and search operations leave files unchanged. Use read_file for one known text file.',
|
|
42
|
+
run_terminal:'Use this when the task needs a shell command or an existing ReMCP terminal session: start, read, wait, interact, stop, or list sessions. Choose exactly one operation; parameters are validated against that operation’s closed schema. Starting or interacting with commands can change local or external state, and force termination can lose unsaved process work; reading, waiting, and listing do not mutate the session.',
|
|
43
|
+
control_computer:'Use this for semantic inspection or control of native desktop applications on this computer: windows, accessibility/UI elements, waits, displays, app launch, text/input, clipboard, open/reveal, notifications, or short screen recording. Snapshot/find/wait operations observe state; clicks, typing, window close, app launch, clipboard writes, and recording can change local application state. Prefer semantic targets before raw coordinates.',
|
|
44
|
+
view_image:'Use this when the result must be pixels from this computer: read an existing image or capture the desktop, one window, monitor, or region. These operations return pixels without interacting with the visible UI. Use control_computer when semantic structure or an action is enough.',
|
|
45
|
+
control_browser:'Use this for a debuggable Chromium page when the task needs tabs, navigation, DOM/accessibility inspection, element search/actions, waits, or explicit JavaScript evaluation. Snapshot, find, and wait operations observe page state; navigation, clicks, typing, uploads, and JavaScript evaluation can change page or remote-site state. Use control_computer for browser chrome or non-CDP applications.',
|
|
46
|
+
manage_system:'Use this for operating-system or ReMCP runtime state: system/runtime/process facts, runtime preferences, services, event logs, networking, installed apps, environment, audio, or explicit power actions. Fact, log, and inventory operations only observe state; process termination, preference/service/audio changes, restart, and shutdown can interrupt work or change the machine. Use run_terminal for general shell commands.',
|
|
47
|
+
manage_documents:'Use this for structured document content: read PDF/DOCX/XLSX, edit DOCX/XLSX directly, or merge, split, extract, or inspect PDFs. DOCX/XLSX edits can replace the input when no separate output is supplied, and PDF write operations create or replace their requested output; read_document itself is read-only. Use read_file for plain line-oriented text.',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const allDefinitions = [...toolDefinitions, ...extendedToolDefinitions];
|
|
51
|
+
const definitionByName = new Map(allDefinitions.map(definition => [definition.name, definition]));
|
|
52
|
+
|
|
53
|
+
function publicDefinition(definition) {
|
|
54
|
+
return {
|
|
55
|
+
name:definition.name,
|
|
56
|
+
title:definition.title,
|
|
57
|
+
description:definition.description,
|
|
58
|
+
inputSchema:structuredClone(definition.inputSchema || { type:'object', properties:{}, additionalProperties:false }),
|
|
59
|
+
outputSchema:structuredClone(definition.outputSchema || TEXT_OUTPUT_SCHEMA),
|
|
60
|
+
annotations:{ ...(definition.annotations || {}) },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function uniqueSchemas(schemas) {
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
return schemas.filter(schema => {
|
|
67
|
+
const key = JSON.stringify(schema);
|
|
68
|
+
if (seen.has(key)) return false;
|
|
69
|
+
seen.add(key);
|
|
70
|
+
return true;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function groupedInputSchema(members) {
|
|
75
|
+
const rules = members.map(name => {
|
|
76
|
+
const definition = definitionByName.get(name);
|
|
77
|
+
if (!definition) throw new Error(`Unknown compact runtime operation: ${name}`);
|
|
78
|
+
const schema = structuredClone(definition.inputSchema || { type:'object', properties:{}, additionalProperties:false });
|
|
79
|
+
const needsParameters = Array.isArray(schema.required) && schema.required.length > 0;
|
|
80
|
+
return {
|
|
81
|
+
if:{ properties:{ operation:{ const:name } }, required:['operation'] },
|
|
82
|
+
then:{
|
|
83
|
+
properties:{ parameters:schema },
|
|
84
|
+
...(needsParameters ? { required:['parameters'] } : {}),
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
type:'object',
|
|
90
|
+
properties:{
|
|
91
|
+
operation:{ type:'string', enum:[...members], description:'Concrete local runtime operation to execute in this domain.' },
|
|
92
|
+
parameters:{ type:'object', description:'Arguments for the selected operation; validated against that operation’s original closed schema.' },
|
|
93
|
+
},
|
|
94
|
+
required:['operation'],
|
|
95
|
+
allOf:rules,
|
|
96
|
+
additionalProperties:false,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function groupedOutputSchema(memberDefinitions) {
|
|
101
|
+
const schemas = uniqueSchemas(memberDefinitions.map(definition => structuredClone(definition.outputSchema || TEXT_OUTPUT_SCHEMA)));
|
|
102
|
+
return schemas.length === 1 ? schemas[0] : { oneOf:schemas };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function groupedDefinition(name, members) {
|
|
106
|
+
const memberDefinitions = members.map(member => {
|
|
107
|
+
const definition = definitionByName.get(member);
|
|
108
|
+
if (!definition) throw new Error(`Missing compact runtime operation: ${member}`);
|
|
109
|
+
return definition;
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
name,
|
|
113
|
+
title:groupTitles[name],
|
|
114
|
+
description:groupDescriptions[name],
|
|
115
|
+
inputSchema:groupedInputSchema(members),
|
|
116
|
+
outputSchema:groupedOutputSchema(memberDefinitions),
|
|
117
|
+
annotations:{
|
|
118
|
+
title:groupTitles[name],
|
|
119
|
+
readOnlyHint:memberDefinitions.every(tool => tool.annotations?.readOnlyHint === true),
|
|
120
|
+
destructiveHint:memberDefinitions.some(tool => tool.annotations?.destructiveHint === true),
|
|
121
|
+
idempotentHint:memberDefinitions.every(tool => tool.annotations?.idempotentHint === true),
|
|
122
|
+
openWorldHint:memberDefinitions.some(tool => tool.annotations?.openWorldHint === true),
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function supportedRuntimeOperationNames() {
|
|
128
|
+
const supportedExtended = new Set((await advertisedExtendedTools()).map(tool => tool.name));
|
|
129
|
+
return new Set([
|
|
130
|
+
...toolDefinitions.map(tool => tool.name),
|
|
131
|
+
...extendedToolDefinitions.filter(tool => supportedExtended.has(tool.name)).map(tool => tool.name),
|
|
132
|
+
]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function compactRuntimeToolDefinitions() {
|
|
136
|
+
const supported = await supportedRuntimeOperationNames();
|
|
137
|
+
const tools = [];
|
|
138
|
+
const readFile = definitionByName.get('read_file');
|
|
139
|
+
if (readFile && supported.has('read_file')) tools.push(publicDefinition(readFile));
|
|
140
|
+
for (const [name, configuredMembers] of Object.entries(compactRuntimeGroups)) {
|
|
141
|
+
const members = configuredMembers.filter(member => supported.has(member));
|
|
142
|
+
if (members.length) tools.push(groupedDefinition(name, members));
|
|
143
|
+
}
|
|
144
|
+
return tools;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function resolveCompactRuntimeCall(name, args = {}, definitions = []) {
|
|
148
|
+
if (name === 'read_file') return { runtimeName:'read_file', runtimeArguments:{ ...args } };
|
|
149
|
+
const definition = definitions.find(tool => tool.name === name);
|
|
150
|
+
if (!definition || !compactRuntimeGroups[name]) throw new Error(`Unknown compact runtime tool: ${name}`);
|
|
151
|
+
const runtimeName = String(args.operation || '').trim();
|
|
152
|
+
const allowed = definition.inputSchema?.properties?.operation?.enum || [];
|
|
153
|
+
if (!allowed.includes(runtimeName)) throw new Error(`Unsupported ${name} operation: ${runtimeName || '(empty)'}`);
|
|
154
|
+
const runtimeArguments = args.parameters && typeof args.parameters === 'object' && !Array.isArray(args.parameters)
|
|
155
|
+
? { ...args.parameters }
|
|
156
|
+
: {};
|
|
157
|
+
return { runtimeName, runtimeArguments };
|
|
158
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { compactRuntimeToolDefinitions, resolveCompactRuntimeCall } from './compact-catalog.mjs';
|
|
2
|
+
import { invokeTool } from './invoke.mjs';
|
|
3
|
+
|
|
4
|
+
function toolConfig(definition, fromJsonSchema) {
|
|
5
|
+
return {
|
|
6
|
+
title:definition.title,
|
|
7
|
+
description:definition.description,
|
|
8
|
+
inputSchema:fromJsonSchema(definition.inputSchema || { type:'object' }),
|
|
9
|
+
...(definition.outputSchema ? { outputSchema:fromJsonSchema(definition.outputSchema) } : {}),
|
|
10
|
+
annotations:definition.annotations,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function startCompactRuntimeMcpServer({ version, instructions, onError = () => {} } = {}) {
|
|
15
|
+
const [{ McpServer, fromJsonSchema }, { serveStdio }] = await Promise.all([
|
|
16
|
+
import('@modelcontextprotocol/server'),
|
|
17
|
+
import('@modelcontextprotocol/server/stdio'),
|
|
18
|
+
]);
|
|
19
|
+
const definitions = await compactRuntimeToolDefinitions();
|
|
20
|
+
let activeServer = null;
|
|
21
|
+
let closed = false;
|
|
22
|
+
|
|
23
|
+
const serverHandle = serveStdio(() => {
|
|
24
|
+
const server = new McpServer(
|
|
25
|
+
{ name:'remcp-runtime-compact', version },
|
|
26
|
+
{
|
|
27
|
+
capabilities:{ tools:{} },
|
|
28
|
+
instructions,
|
|
29
|
+
cacheHints:{
|
|
30
|
+
'tools/list':{ ttlMs:3000, cacheScope:'private' },
|
|
31
|
+
'server/discover':{ ttlMs:3000, cacheScope:'private' },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
activeServer = server;
|
|
36
|
+
for (const definition of definitions) {
|
|
37
|
+
server.registerTool(
|
|
38
|
+
definition.name,
|
|
39
|
+
toolConfig(definition, fromJsonSchema),
|
|
40
|
+
async (args, context) => {
|
|
41
|
+
try {
|
|
42
|
+
const resolved = resolveCompactRuntimeCall(definition.name, args || {}, definitions);
|
|
43
|
+
return await invokeTool(resolved.runtimeName, resolved.runtimeArguments, { signal:context?.mcpReq?.signal });
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return { content:[{ type:'text', text:error instanceof Error ? error.message : String(error) }], isError:true };
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return server;
|
|
51
|
+
}, {
|
|
52
|
+
legacy:'serve',
|
|
53
|
+
onerror(error) { onError(error instanceof Error ? error : new Error(String(error))); },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
definitions,
|
|
58
|
+
async notification(notification) {
|
|
59
|
+
if (!activeServer) return;
|
|
60
|
+
await activeServer.server.notification(notification);
|
|
61
|
+
},
|
|
62
|
+
async close() {
|
|
63
|
+
if (closed) return;
|
|
64
|
+
closed = true;
|
|
65
|
+
await serverHandle.close();
|
|
66
|
+
activeServer = null;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
package/src/compact.mjs
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { configurationError } from './config.mjs';
|
|
4
|
+
import { compactRuntimeToolDefinitions } from './compact-catalog.mjs';
|
|
5
|
+
import { shutdownSessions, startSessionSweeper } from './sessions.mjs';
|
|
6
|
+
import { VERSION } from './version.mjs';
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
|
|
10
|
+
async function writeStdout(value) {
|
|
11
|
+
await new Promise((resolve, reject) => process.stdout.write(value, error => error ? reject(error) : resolve()));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
15
|
+
process.stdout.write([
|
|
16
|
+
'ReMCP compact local runtime',
|
|
17
|
+
'',
|
|
18
|
+
'Usage: remcp-runtime-compact [--print-tools] [--describe] [--version]',
|
|
19
|
+
'',
|
|
20
|
+
'This stdio MCP server exposes the same local ReMCP runtime through a compact verb_noun facade.',
|
|
21
|
+
'It is intended for directory scanners, local MCP clients, and environments where a small tool surface improves selection.',
|
|
22
|
+
'',
|
|
23
|
+
].join('\n'));
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (args.includes('--version')) { process.stdout.write(`${VERSION}\n`); process.exit(0); }
|
|
28
|
+
|
|
29
|
+
const tools = await compactRuntimeToolDefinitions();
|
|
30
|
+
if (args.includes('--print-tools')) { await writeStdout(`${JSON.stringify(tools, null, 2)}\n`); process.exit(0); }
|
|
31
|
+
if (args.includes('--describe')) {
|
|
32
|
+
await writeStdout(`${JSON.stringify({ version:VERSION, tools:tools.length, toolNames:tools.map(tool => tool.name) }, null, 2)}\n`);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const configProblem = configurationError();
|
|
37
|
+
if (configProblem) { console.error(`ReMCP compact runtime refuses to start: ${configProblem}`); process.exit(2); }
|
|
38
|
+
|
|
39
|
+
const instructions = [
|
|
40
|
+
'This compact runtime executes real ReMCP operations locally on this computer.',
|
|
41
|
+
'Choose one domain tool, then one operation. Inspect before changing state and use the least invasive operation that satisfies the request.',
|
|
42
|
+
'The parameters object is validated against the selected operation’s original closed schema.',
|
|
43
|
+
].join(' ');
|
|
44
|
+
|
|
45
|
+
const { startCompactRuntimeMcpServer } = await import('./compact-mcp.mjs');
|
|
46
|
+
const runtimeServer = await startCompactRuntimeMcpServer({
|
|
47
|
+
version:VERSION,
|
|
48
|
+
instructions,
|
|
49
|
+
onError(error) { console.error(`ReMCP compact runtime MCP error: ${error instanceof Error ? error.message : String(error)}`); },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
let shutdownStarted = false;
|
|
53
|
+
async function shutdown(code = 0) {
|
|
54
|
+
if (shutdownStarted) return;
|
|
55
|
+
shutdownStarted = true;
|
|
56
|
+
shutdownSessions();
|
|
57
|
+
try { await runtimeServer.close(); } catch {}
|
|
58
|
+
process.exit(code);
|
|
59
|
+
}
|
|
60
|
+
process.stdout.on('error', error => {
|
|
61
|
+
if (error?.code === 'EPIPE' || error?.code === 'ERR_STREAM_DESTROYED') void shutdown(0);
|
|
62
|
+
else console.error(`ReMCP compact runtime stdout error: ${error instanceof Error ? error.message : String(error)}`);
|
|
63
|
+
});
|
|
64
|
+
process.on('SIGINT', () => void shutdown(0));
|
|
65
|
+
process.on('SIGTERM', () => void shutdown(0));
|
|
66
|
+
process.on('exit', () => { shutdownSessions(); });
|
|
67
|
+
startSessionSweeper();
|
|
68
|
+
console.error(`ReMCP compact runtime ${VERSION} ready with ${tools.length} domain tools`);
|