@remcp/runtime 0.2.48 → 0.2.49
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 +8 -0
- package/README.md +4 -2
- package/package.json +1 -1
- package/src/catalog.mjs +72 -0
- package/src/tools/terminal.mjs +58 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.49 — 2026-09-20
|
|
6
|
+
|
|
7
|
+
- Terminal process tools now return schema-validated structured session state, output ranges, match results and termination facts alongside their human-readable text.
|
|
8
|
+
- The runtime catalog and hosted tool contract align on bounded structured outputs and accurate read-only/destructive/idempotent/open-world annotations for the expanded computer-use surface.
|
|
9
|
+
- Preview references remain optional presentation metadata rather than replacing the source result used for model reasoning.
|
|
10
|
+
- Lockstep patch release with the ReMCP client at `0.2.49`.
|
|
11
|
+
|
|
5
12
|
## 0.2.48 — 2026-09-20
|
|
6
13
|
|
|
7
14
|
- Migrate the local stdio runtime to the official `@modelcontextprotocol/server` v2 serving entry while preserving legacy 2025-era client compatibility.
|
|
@@ -13,6 +20,7 @@
|
|
|
13
20
|
- Added high-level `computer_snapshot` and `computer_action` primitives so agents can prefer semantic Accessibility/DOM targets and fall back to coordinates/screenshots only when necessary.
|
|
14
21
|
- Chrome DevTools Protocol endpoints are restricted to loopback addresses. New filesystem/document operations continue through the existing resolved-path allowlist, and launch/service/power operations reuse the runtime command policy.
|
|
15
22
|
- Fixed metadata output truncation once the JSON tool contract exceeded the operating-system pipe buffer by waiting for stdout writes to flush before exit.
|
|
23
|
+
- Terminal session tools now expose schema-validated structured `pid`, status, exit information, output ranges/matches, and session lists alongside their human-readable text, so hosted agents can chain process calls without parsing prose.
|
|
16
24
|
- No browser/document rendering stack was added; the runtime remains dependency-light and uses native OS facilities plus existing qpdf/poppler/zip helpers when available.
|
|
17
25
|
|
|
18
26
|
## 0.2.47 — 2026-09-19
|
package/README.md
CHANGED
|
@@ -49,9 +49,11 @@ support and emits `notifications/tools/list_changed` when that capability set ch
|
|
|
49
49
|
| Diagnostics | `service`, `event_log`, `network`, `installed_apps`, `environment`, `audio`, `power_action`, `record_screen` |
|
|
50
50
|
| Documents | `read_document`, `edit_spreadsheet`, `edit_document`, `pdf_action` |
|
|
51
51
|
|
|
52
|
-
The hosted ReMCP endpoint adds
|
|
52
|
+
The hosted ReMCP endpoint adds eleven account/presentation tools — `list_devices`, `ping_device`,
|
|
53
53
|
`who_am_i`, `create_pairing_command`, `get_recent_tool_calls`, `get_usage_statistics`,
|
|
54
|
-
`get_configuration`,
|
|
54
|
+
`get_configuration`, `render_file_preview`, `render_image_preview`, `render_terminal_preview`, and
|
|
55
|
+
`rename_device` — for a 94-tool MCP Apps catalog. Plain MCP clients receive 91 tools because the
|
|
56
|
+
three presentation-only render tools are omitted unless the UI extension is negotiated.
|
|
55
57
|
|
|
56
58
|
`computer_snapshot` and `computer_action` keep OCR inside the compact high-level surface: when a local `tesseract` binary is available, snapshot can return bounded OCR text/boxes and click targeting can fall back through Accessibility → browser DOM/CDP → OCR → coordinates. OCR is optional and never adds another MCP tool or bundled OCR dependency.
|
|
57
59
|
|
package/package.json
CHANGED
package/src/catalog.mjs
CHANGED
|
@@ -16,6 +16,72 @@ export const TEXT_OUTPUT_SCHEMA = {
|
|
|
16
16
|
additionalProperties: false,
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
const NULLABLE_NUMBER_SCHEMA = { anyOf:[{ type:'number' }, { type:'null' }] };
|
|
20
|
+
const NULLABLE_STRING_SCHEMA = { anyOf:[{ type:'string' }, { type:'null' }] };
|
|
21
|
+
const TERMINAL_SESSION_PROPERTIES = {
|
|
22
|
+
pid:{ type:'number', description:'Process/session id used by follow-up terminal tools.' },
|
|
23
|
+
status:{ type:'string', description:'running or exited, with an exit code/signal when known.' },
|
|
24
|
+
runtimeMs:{ type:'number' },
|
|
25
|
+
lines:{ type:'number', description:'Total output lines produced by the session.' },
|
|
26
|
+
exited:{ type:'boolean' },
|
|
27
|
+
exitCode:NULLABLE_NUMBER_SCHEMA,
|
|
28
|
+
signal:NULLABLE_STRING_SCHEMA,
|
|
29
|
+
};
|
|
30
|
+
function terminalOutputSchema(extraProperties = {}, extraRequired = []) {
|
|
31
|
+
return {
|
|
32
|
+
type:'object',
|
|
33
|
+
properties:{ ...TERMINAL_SESSION_PROPERTIES, ...extraProperties },
|
|
34
|
+
required:['pid','status','runtimeMs','lines','exited','exitCode','signal', ...extraRequired],
|
|
35
|
+
additionalProperties:false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const START_PROCESS_OUTPUT_SCHEMA = terminalOutputSchema({
|
|
39
|
+
command:{ type:'string' },
|
|
40
|
+
output:{ type:'string' },
|
|
41
|
+
partial:{ type:'string' },
|
|
42
|
+
warning:{ type:'string' },
|
|
43
|
+
}, ['command','output']);
|
|
44
|
+
const READ_PROCESS_OUTPUT_SCHEMA = terminalOutputSchema({
|
|
45
|
+
range:{ type:'string' },
|
|
46
|
+
output:{ type:'string' },
|
|
47
|
+
explicitOffset:{ type:'boolean' },
|
|
48
|
+
}, ['range','output','explicitOffset']);
|
|
49
|
+
const WAIT_PROCESS_OUTPUT_SCHEMA = terminalOutputSchema({
|
|
50
|
+
pattern:{ type:'string' },
|
|
51
|
+
matched:{ type:'boolean' },
|
|
52
|
+
bufferedMatch:{ type:'boolean' },
|
|
53
|
+
timeoutMs:{ type:'number' },
|
|
54
|
+
output:{ type:'string' },
|
|
55
|
+
}, ['pattern','matched','bufferedMatch','timeoutMs','output']);
|
|
56
|
+
const INTERACT_PROCESS_OUTPUT_SCHEMA = terminalOutputSchema({
|
|
57
|
+
output:{ type:'string' },
|
|
58
|
+
}, ['output']);
|
|
59
|
+
const TERMINATE_PROCESS_OUTPUT_SCHEMA = terminalOutputSchema({
|
|
60
|
+
terminated:{ type:'boolean' },
|
|
61
|
+
alreadyExited:{ type:'boolean' },
|
|
62
|
+
escalated:{ type:'boolean' },
|
|
63
|
+
}, ['terminated','alreadyExited','escalated']);
|
|
64
|
+
const LIST_SESSIONS_OUTPUT_SCHEMA = {
|
|
65
|
+
type:'object',
|
|
66
|
+
properties:{
|
|
67
|
+
sessions:{
|
|
68
|
+
type:'array',
|
|
69
|
+
items:{
|
|
70
|
+
type:'object',
|
|
71
|
+
properties:{
|
|
72
|
+
...TERMINAL_SESSION_PROPERTIES,
|
|
73
|
+
blocked:{ type:'string' },
|
|
74
|
+
command:{ type:'string' },
|
|
75
|
+
},
|
|
76
|
+
required:['pid','status','runtimeMs','lines','exited','exitCode','signal','blocked','command'],
|
|
77
|
+
additionalProperties:false,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
required:['sessions'],
|
|
82
|
+
additionalProperties:false,
|
|
83
|
+
};
|
|
84
|
+
|
|
19
85
|
const readOnly = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
20
86
|
const readOnlyNonIdempotent = { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
21
87
|
const additive = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
@@ -606,6 +672,7 @@ export const toolDefinitions = [
|
|
|
606
672
|
additionalProperties: false,
|
|
607
673
|
},
|
|
608
674
|
annotations: command,
|
|
675
|
+
outputSchema: START_PROCESS_OUTPUT_SCHEMA,
|
|
609
676
|
handler: terminalToolHandlers.start_process,
|
|
610
677
|
},
|
|
611
678
|
{
|
|
@@ -626,6 +693,7 @@ export const toolDefinitions = [
|
|
|
626
693
|
// Reading without an explicit offset advances the session cursor, so retries can return
|
|
627
694
|
// different output even after the process exits. It never modifies user files.
|
|
628
695
|
annotations: readOnlyNonIdempotent,
|
|
696
|
+
outputSchema: READ_PROCESS_OUTPUT_SCHEMA,
|
|
629
697
|
handler: terminalToolHandlers.read_process_output,
|
|
630
698
|
},
|
|
631
699
|
{
|
|
@@ -643,6 +711,7 @@ export const toolDefinitions = [
|
|
|
643
711
|
additionalProperties: false,
|
|
644
712
|
},
|
|
645
713
|
annotations: command,
|
|
714
|
+
outputSchema: INTERACT_PROCESS_OUTPUT_SCHEMA,
|
|
646
715
|
handler: terminalToolHandlers.interact_with_process,
|
|
647
716
|
},
|
|
648
717
|
{
|
|
@@ -660,6 +729,7 @@ export const toolDefinitions = [
|
|
|
660
729
|
additionalProperties: false,
|
|
661
730
|
},
|
|
662
731
|
annotations: readOnlyNonIdempotent,
|
|
732
|
+
outputSchema: WAIT_PROCESS_OUTPUT_SCHEMA,
|
|
663
733
|
handler: terminalToolHandlers.wait_for_process_output,
|
|
664
734
|
},
|
|
665
735
|
{
|
|
@@ -675,6 +745,7 @@ export const toolDefinitions = [
|
|
|
675
745
|
additionalProperties: false,
|
|
676
746
|
},
|
|
677
747
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
748
|
+
outputSchema: TERMINATE_PROCESS_OUTPUT_SCHEMA,
|
|
678
749
|
handler: terminalToolHandlers.force_terminate,
|
|
679
750
|
},
|
|
680
751
|
{
|
|
@@ -683,6 +754,7 @@ export const toolDefinitions = [
|
|
|
683
754
|
description: 'List terminal sessions started during this ReMCP runtime session with their status and how long they have been running.',
|
|
684
755
|
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
685
756
|
annotations: readOnly,
|
|
757
|
+
outputSchema: LIST_SESSIONS_OUTPUT_SCHEMA,
|
|
686
758
|
handler: terminalToolHandlers.list_sessions,
|
|
687
759
|
},
|
|
688
760
|
{
|
package/src/tools/terminal.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
waitForProcessExit,
|
|
18
18
|
waitForProcessActivity,
|
|
19
19
|
} from '../sessions.mjs';
|
|
20
|
-
import { clampInteger, fail, requireInteger, requireString, text } from '../util.mjs';
|
|
20
|
+
import { clampInteger, fail, requireInteger, requireString, structured, text } from '../util.mjs';
|
|
21
21
|
|
|
22
22
|
function shellCommand() {
|
|
23
23
|
if (runtimeConfig.defaultShell) return runtimeConfig.defaultShell;
|
|
@@ -32,7 +32,15 @@ function shellArgs(command) {
|
|
|
32
32
|
|
|
33
33
|
function describeSession(session) {
|
|
34
34
|
const status = session.exited ? `exited${session.signal ? ` (${session.signal})` : session.exitCode === null ? '' : ` (code ${session.exitCode})`}` : 'running';
|
|
35
|
-
return {
|
|
35
|
+
return {
|
|
36
|
+
pid: session.pid,
|
|
37
|
+
status,
|
|
38
|
+
runtimeMs: (session.finishedAt || Date.now()) - session.startedAt,
|
|
39
|
+
lines: totalLines(session),
|
|
40
|
+
exited: Boolean(session.exited),
|
|
41
|
+
exitCode: session.exitCode ?? null,
|
|
42
|
+
signal: session.signal ?? null,
|
|
43
|
+
};
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
function abortError() {
|
|
@@ -90,8 +98,16 @@ export async function startProcessTool(args, extra = {}) {
|
|
|
90
98
|
const partial = session.partial;
|
|
91
99
|
session.cursor = session.droppedLines + session.lines.length;
|
|
92
100
|
session.lastPartialRead = session.partial || null;
|
|
93
|
-
const warning = verdict.note ?
|
|
94
|
-
|
|
101
|
+
const warning = verdict.note ? String(verdict.note) : '';
|
|
102
|
+
const rendered = [warning, headline, output, partial].filter(Boolean).join('\n');
|
|
103
|
+
const status = describeSession(session);
|
|
104
|
+
return structured({
|
|
105
|
+
...status,
|
|
106
|
+
command,
|
|
107
|
+
output,
|
|
108
|
+
...(partial ? { partial } : {}),
|
|
109
|
+
...(warning ? { warning } : {}),
|
|
110
|
+
}, rendered);
|
|
95
111
|
}
|
|
96
112
|
|
|
97
113
|
export async function readProcessOutputTool(args, extra = {}) {
|
|
@@ -124,7 +140,13 @@ export async function readProcessOutputTool(args, extra = {}) {
|
|
|
124
140
|
}
|
|
125
141
|
const status = describeSession(session);
|
|
126
142
|
const header = `pid ${pid} ${status.status} · lines ${range}`;
|
|
127
|
-
|
|
143
|
+
const output = slice.join('\n');
|
|
144
|
+
return structured({
|
|
145
|
+
...status,
|
|
146
|
+
range,
|
|
147
|
+
output,
|
|
148
|
+
explicitOffset:hasOffset,
|
|
149
|
+
}, `${header}\n${output}`);
|
|
128
150
|
}
|
|
129
151
|
|
|
130
152
|
function compileWaiter(pattern) {
|
|
@@ -164,7 +186,15 @@ export async function waitForProcessOutputTool(args, extra = {}) {
|
|
|
164
186
|
const headline = matched
|
|
165
187
|
? `pid ${pid} ${status.status} · pattern matched${bufferedMatch ? ' (already buffered)' : ''}`
|
|
166
188
|
: `pid ${pid} ${status.status} · pattern not matched within ${timeoutMs}ms`;
|
|
167
|
-
|
|
189
|
+
const output = slice.join('\n');
|
|
190
|
+
return structured({
|
|
191
|
+
...status,
|
|
192
|
+
pattern,
|
|
193
|
+
matched,
|
|
194
|
+
bufferedMatch,
|
|
195
|
+
timeoutMs,
|
|
196
|
+
output,
|
|
197
|
+
}, [headline, output].filter(Boolean).join('\n'));
|
|
168
198
|
}
|
|
169
199
|
|
|
170
200
|
export async function interactWithProcessTool(args, extra = {}) {
|
|
@@ -195,34 +225,49 @@ export async function interactWithProcessTool(args, extra = {}) {
|
|
|
195
225
|
}
|
|
196
226
|
const slice = readNewOutput(session);
|
|
197
227
|
const status = describeSession(session);
|
|
198
|
-
|
|
228
|
+
const output = slice.join('\n');
|
|
229
|
+
return structured({
|
|
230
|
+
...status,
|
|
231
|
+
output,
|
|
232
|
+
}, [`pid ${pid} ${status.status}`, output].filter(Boolean).join('\n'));
|
|
199
233
|
}
|
|
200
234
|
|
|
201
235
|
export async function forceTerminateTool(args) {
|
|
202
236
|
const pid = requireInteger(args.pid, 'pid');
|
|
203
237
|
const session = getProcessSession(pid);
|
|
204
238
|
if (!session) fail(`No ReMCP session with pid ${pid}`);
|
|
205
|
-
if (session.exited)
|
|
239
|
+
if (session.exited) {
|
|
240
|
+
const status = describeSession(session);
|
|
241
|
+
return structured({ ...status, terminated:false, alreadyExited:true, escalated:false }, `Process ${pid} already exited.`);
|
|
242
|
+
}
|
|
206
243
|
killSessionTree(session, 'SIGTERM');
|
|
207
244
|
const deadline = Date.now() + 2000;
|
|
208
245
|
while (!session.exited && Date.now() < deadline) await waitForProcessActivity(session, 100);
|
|
246
|
+
let escalated = false;
|
|
209
247
|
if (!session.exited) {
|
|
248
|
+
escalated = true;
|
|
210
249
|
killSessionTree(session, 'SIGKILL');
|
|
211
250
|
await waitForProcessActivity(session, 1000);
|
|
212
251
|
}
|
|
213
252
|
const status = describeSession(session);
|
|
214
|
-
return
|
|
253
|
+
return structured(
|
|
254
|
+
{ ...status, terminated:Boolean(session.exited), alreadyExited:false, escalated },
|
|
255
|
+
`Terminated session ${pid}${status.status.startsWith('exited') ? '' : ' (still running)'}. Status: ${status.status}.`,
|
|
256
|
+
);
|
|
215
257
|
}
|
|
216
258
|
|
|
217
259
|
export async function listSessionsTool() {
|
|
218
260
|
const sessions = listProcessSessions();
|
|
219
|
-
|
|
220
|
-
const rows = sessions.map(session => {
|
|
261
|
+
const facts = sessions.map(session => {
|
|
221
262
|
const status = describeSession(session);
|
|
222
263
|
const blocked = session.exited ? '' : session.partial ? 'blocked-possibly' : 'idle-or-running';
|
|
223
|
-
return
|
|
264
|
+
return { ...status, blocked, command:session.command.slice(0, 120) };
|
|
224
265
|
});
|
|
225
|
-
|
|
266
|
+
if (!facts.length) return structured({ sessions:[] }, 'No active terminal sessions.');
|
|
267
|
+
const rows = facts.map(session =>
|
|
268
|
+
`pid ${session.pid} · ${session.status} · ${Math.round(session.runtimeMs / 1000)}s · ${session.blocked} · ${session.command}`
|
|
269
|
+
);
|
|
270
|
+
return structured({ sessions:facts }, rows.join('\n'));
|
|
226
271
|
}
|
|
227
272
|
|
|
228
273
|
export const terminalToolHandlers = {
|