@remcp/runtime 0.2.122 → 0.2.124
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 +12 -0
- package/README.md +4 -3
- package/package.json +1 -1
- package/src/compact-mcp.mjs +100 -7
- package/src/documents.mjs +9 -6
- package/src/extended/catalog.mjs +19 -4
- package/src/extended/documents.mjs +22 -3
- package/src/mcp-v2.mjs +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.124 — 2026-10-01
|
|
4
|
+
|
|
5
|
+
- P3 document capability discovery is cross-platform: Windows uses the native .NET OOXML archive backend, while macOS/Linux require both `zip` and `unzip` before advertising the XLSX/DOCX editor tools.
|
|
6
|
+
- `read_document` and `pdf_action` remain discoverable on supported desktop platforms even without an OOXML archive backend; DOCX/XLSX read attempts still fail closed at execution time when extraction is unavailable. `edit_spreadsheet` and `edit_document` explicitly require `ooxml`.
|
|
7
|
+
- Windows and macOS release CI run real candidate XLSX/DOCX create/read round trips before publication. The 83-tool names and schemas are unchanged.
|
|
8
|
+
|
|
9
|
+
## 0.2.123 — 2026-10-01
|
|
10
|
+
|
|
11
|
+
- 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.
|
|
12
|
+
- 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.
|
|
13
|
+
- The underlying 83 granular runtime tool names, input/output schemas, annotations, and handlers remain unchanged from 0.2.122.
|
|
14
|
+
|
|
3
15
|
## 0.2.122 — 2026-09-30
|
|
4
16
|
|
|
5
17
|
- 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
|
|
|
@@ -67,7 +67,8 @@ For frontend QA, use semantic data to operate the page and rendered pixels to ve
|
|
|
67
67
|
`set_config_value` remains deliberately narrow: it can change only `telemetryEnabled`,
|
|
68
68
|
`maxReadLines`, `maxBufferedLines`, and `maxOutputBytes`. Access roots, blocked commands,
|
|
69
69
|
the command guardrail, shell, write limit, runtime name, and unrestricted mode stay with the person
|
|
70
|
-
at the computer. DOCX/XLSX edits operate directly on OOXML
|
|
70
|
+
at the computer. DOCX/XLSX edits operate directly on OOXML: Windows uses the built-in .NET ZIP backend,
|
|
71
|
+
while macOS/Linux advertise the OOXML editor tools only when both `zip` and `unzip` are available. `read_document` stays discoverable for PDF/plain-text formats when that backend is absent and fails closed only for DOCX/XLSX reads. Structural PDF writes use existing
|
|
71
72
|
system tools such as qpdf/poppler when present. PDF text reading uses the built-in parser first and can fall back to local `pdftotext` for embedded-font PDFs; ReMCP still does not bundle Chromium, Puppeteer,
|
|
72
73
|
`sharp`, or `exceljs`.
|
|
73
74
|
|
|
@@ -81,7 +82,7 @@ example: they are enabled only while the runtime can use a supported loopback br
|
|
|
81
82
|
The generated hosted reference covers the current **10 hosted production definitions**, all model-visible
|
|
82
83
|
and routing to the 83 granular runtime operations described above; the optional widget-enabled surface is 15.
|
|
83
84
|
The separate compact local stdio entrypoint advertises `read_file` plus up to seven domain tools
|
|
84
|
-
(up to 8 tools total)
|
|
85
|
+
(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
86
|
See https://remcp.site/docs#tools and public `docs/TOOLS.md`.
|
|
86
87
|
|
|
87
88
|
## No approval staircase
|
package/package.json
CHANGED
package/src/compact-mcp.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
|
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/documents.mjs
CHANGED
|
@@ -40,7 +40,7 @@ function unzipEntry(buffer, wanted) {
|
|
|
40
40
|
const extraLength = buffer.readUInt16LE(offset + 30);
|
|
41
41
|
const commentLength = buffer.readUInt16LE(offset + 32);
|
|
42
42
|
const localOffset = buffer.readUInt32LE(offset + 42);
|
|
43
|
-
const name = buffer.toString('utf8', offset + 46, offset + 46 + nameLength);
|
|
43
|
+
const name = buffer.toString('utf8', offset + 46, offset + 46 + nameLength).replace(/\\/g, '/');
|
|
44
44
|
if (name === wanted) {
|
|
45
45
|
const localNameLength = buffer.readUInt16LE(localOffset + 26);
|
|
46
46
|
const localExtraLength = buffer.readUInt16LE(localOffset + 28);
|
|
@@ -116,16 +116,19 @@ function wordXmlText(xml) {
|
|
|
116
116
|
return output;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
export function
|
|
120
|
-
|
|
121
|
-
if (!document) throw new Error('This file is not a readable .docx (its word/document.xml is missing or compressed in an unsupported way)');
|
|
122
|
-
const xml = document.toString('utf8');
|
|
123
|
-
return wordXmlText(xml)
|
|
119
|
+
export function readDocxXmlText(xml) {
|
|
120
|
+
return wordXmlText(String(xml))
|
|
124
121
|
.replace(/&(amp|lt|gt|quot|apos);/g, match => DOCX_ENTITIES[match])
|
|
125
122
|
.replace(/\n{3,}/g, '\n\n')
|
|
126
123
|
.trim();
|
|
127
124
|
}
|
|
128
125
|
|
|
126
|
+
export function readDocxText(buffer) {
|
|
127
|
+
const document = unzipEntry(buffer, 'word/document.xml');
|
|
128
|
+
if (!document) throw new Error('This file is not a readable .docx (its word/document.xml is missing or compressed in an unsupported way)');
|
|
129
|
+
return readDocxXmlText(document.toString('utf8'));
|
|
130
|
+
}
|
|
131
|
+
|
|
129
132
|
function decodePdfString(raw) {
|
|
130
133
|
// Literal strings arrive with backslash escapes; hex strings are pairs of hex digits.
|
|
131
134
|
return raw
|
package/src/extended/catalog.mjs
CHANGED
|
@@ -569,8 +569,8 @@ export const extendedToolDefinitions = [
|
|
|
569
569
|
define('record_screen', 'Record screen', 'Record a short bounded desktop video to a permitted local file for debugging and return its path and size. Uses native/available recording helpers.', o({ duration_seconds:n('1..120 seconds.'), fps:n('1..60.'), destination:s('Permitted output path. If an extension is supplied, use .webm for native GNOME Wayland recording and .mp4 for the other recorder backends.') }), mutating, diagnosticHandlers.record_screen, ['record']),
|
|
570
570
|
|
|
571
571
|
define('read_document', 'Read document', 'Read PDF, DOCX, XLSX, TXT, Markdown, CSV, JSON, or XML. PDF/DOCX extraction is built in and XLSX is parsed from its OOXML worksheet cells.', o({ path:s('Document path.'), sheet:s('Worksheet name for XLSX.'), max_cells:n() }, ['path']), readOnly, documentHandlers.read_document, ['documents']),
|
|
572
|
-
define('edit_spreadsheet', 'Edit spreadsheet', 'Create a new XLSX workbook or edit up to 500 expanded XLSX cells directly in OOXML without launching Excel. For creation set create=true and use path as a new destination; existing files are never overwritten by create mode. Each edit may target one cell or a rectangular range, with a scalar fill, a values matrix, or a formula.', o({ path:s('Existing .xlsx path, or the new workbook destination when create=true.'), create:b('Create a new XLSX at path. Fails if path already exists; do not combine with output.'), output:s('Optional output .xlsx when editing an existing workbook; defaults to replacing input.'), sheet:s('Worksheet name. For create=true, defaults to Sheet1 and becomes the new workbook sheet name.'), edits:{ type:'array', maxItems:500, items:o({ cell:s('Single A1 cell reference.'), range:s('Rectangular range such as A1:C3.'), value:{ description:'Scalar value for one cell or to fill a range.' }, values:{ type:'array', items:{ type:'array', items:{} }, description:'2D matrix matching the range dimensions.' }, formula:s('Optional formula; for a range it is written to each expanded cell.') }) } }, ['path','edits']), mutating, documentHandlers.edit_spreadsheet, ['documents']),
|
|
573
|
-
define('edit_document', 'Edit DOCX document', 'Create a new DOCX document or edit DOCX paragraph structure directly in OOXML: replace text, append/prepend paragraphs, insert before/after matching paragraphs, or delete matching paragraphs. For creation set create=true and use path as a new destination; existing files are never overwritten by create mode.', o({ path:s('Existing .docx path, or the new document destination when create=true.'), create:b('Create a new DOCX at path. Fails if path already exists; do not combine with output.'), output:s('Optional output .docx path when editing an existing document.'), operations:{ type:'array', maxItems:100, items:o({ action:e(['replace','append_paragraph','prepend_paragraph','insert_paragraph_before','insert_paragraph_after','delete_paragraph']), search:s('Paragraph text substring for replace/insert/delete operations.'), replacement:s(), all:b('Apply to all matching paragraphs; default true.'), text:s('Paragraph text to append/prepend/insert.') }, ['action']) } }, ['path','operations']), mutating, documentHandlers.edit_document, ['documents']),
|
|
572
|
+
define('edit_spreadsheet', 'Edit spreadsheet', 'Create a new XLSX workbook or edit up to 500 expanded XLSX cells directly in OOXML without launching Excel. For creation set create=true and use path as a new destination; existing files are never overwritten by create mode. Each edit may target one cell or a rectangular range, with a scalar fill, a values matrix, or a formula.', o({ path:s('Existing .xlsx path, or the new workbook destination when create=true.'), create:b('Create a new XLSX at path. Fails if path already exists; do not combine with output.'), output:s('Optional output .xlsx when editing an existing workbook; defaults to replacing input.'), sheet:s('Worksheet name. For create=true, defaults to Sheet1 and becomes the new workbook sheet name.'), edits:{ type:'array', maxItems:500, items:o({ cell:s('Single A1 cell reference.'), range:s('Rectangular range such as A1:C3.'), value:{ description:'Scalar value for one cell or to fill a range.' }, values:{ type:'array', items:{ type:'array', items:{} }, description:'2D matrix matching the range dimensions.' }, formula:s('Optional formula; for a range it is written to each expanded cell.') }) } }, ['path','edits']), mutating, documentHandlers.edit_spreadsheet, ['documents','ooxml']),
|
|
573
|
+
define('edit_document', 'Edit DOCX document', 'Create a new DOCX document or edit DOCX paragraph structure directly in OOXML: replace text, append/prepend paragraphs, insert before/after matching paragraphs, or delete matching paragraphs. For creation set create=true and use path as a new destination; existing files are never overwritten by create mode.', o({ path:s('Existing .docx path, or the new document destination when create=true.'), create:b('Create a new DOCX at path. Fails if path already exists; do not combine with output.'), output:s('Optional output .docx path when editing an existing document.'), operations:{ type:'array', maxItems:100, items:o({ action:e(['replace','append_paragraph','prepend_paragraph','insert_paragraph_before','insert_paragraph_after','delete_paragraph']), search:s('Paragraph text substring for replace/insert/delete operations.'), replacement:s(), all:b('Apply to all matching paragraphs; default true.'), text:s('Paragraph text to append/prepend/insert.') }, ['action']) } }, ['path','operations']), mutating, documentHandlers.edit_document, ['documents','ooxml']),
|
|
574
574
|
define('pdf_action', 'PDF action', 'Inspect PDF annotations or metadata, merge PDFs, split a PDF into pages, or extract selected page ranges. Structural writes use qpdf/poppler/pdftk when installed.', o({ action:e(['merge','split','extract_pages','annotations','info']), path:s(), paths:{type:'array',items:{type:'string'}}, output:s(), output_dir:s(), pattern:s(), pages:s('Page list/ranges such as 1-3,5.') }, ['action']), mutating, documentHandlers.pdf_action, ['documents']),
|
|
575
575
|
];
|
|
576
576
|
|
|
@@ -598,6 +598,21 @@ async function linuxHasPyAtSpi() {
|
|
|
598
598
|
return pyAtSpiCache.value;
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
+
export function documentCapabilitySnapshot({
|
|
602
|
+
platform = process.platform,
|
|
603
|
+
commandExistsFn = commandExists,
|
|
604
|
+
} = {}) {
|
|
605
|
+
const supportedPlatform = ['win32','darwin','linux'].includes(platform);
|
|
606
|
+
const nativeOoxml = platform === 'win32';
|
|
607
|
+
const archiveOoxml = (platform === 'darwin' || platform === 'linux')
|
|
608
|
+
&& commandExistsFn('zip')
|
|
609
|
+
&& commandExistsFn('unzip');
|
|
610
|
+
return {
|
|
611
|
+
documents:supportedPlatform,
|
|
612
|
+
ooxml:supportedPlatform && (nativeOoxml || archiveOoxml),
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
601
616
|
export async function capabilitySnapshot() {
|
|
602
617
|
const win = process.platform === 'win32';
|
|
603
618
|
const mac = process.platform === 'darwin';
|
|
@@ -608,6 +623,7 @@ export async function capabilitySnapshot() {
|
|
|
608
623
|
const portalInput = wayland && waylandPortalCandidate();
|
|
609
624
|
const x11Input = linux && !wayland && (commandExists('wdotool') || commandExists('xdotool'));
|
|
610
625
|
const browserCdp = browserRemoteEnabled() && await browserControlAvailable(undefined, 500);
|
|
626
|
+
const documentCapabilities = documentCapabilitySnapshot();
|
|
611
627
|
return {
|
|
612
628
|
windows: win || mac || commandExists('wdotool') || commandExists('wmctrl') || (wayland && pyAtSpi),
|
|
613
629
|
ui: win || mac || pyAtSpi,
|
|
@@ -620,7 +636,6 @@ export async function capabilitySnapshot() {
|
|
|
620
636
|
notifications: win || mac || commandExists('notify-send'),
|
|
621
637
|
browser_cdp: browserCdp,
|
|
622
638
|
browser_evaluate: browserCdp && (process.env.NODE_ENV !== 'production' || ['1', 'true', 'yes', 'on'].includes(String(process.env.REMCP_BROWSER_ALLOW_EVALUATE || '').toLowerCase())),
|
|
623
|
-
documents: linux,
|
|
624
639
|
services: win || mac || commandExists('systemctl'),
|
|
625
640
|
|
|
626
641
|
logs: win || mac || commandExists('journalctl'),
|
|
@@ -628,7 +643,7 @@ export async function capabilitySnapshot() {
|
|
|
628
643
|
audio: win || mac || commandExists('wpctl') || commandExists('pactl') || commandExists('amixer'),
|
|
629
644
|
power: win || mac || commandExists('systemctl') || commandExists('loginctl'),
|
|
630
645
|
record: recordScreenAvailable({ platform:process.platform, wayland, gnomeSupported:gnomeRecord }),
|
|
631
|
-
|
|
646
|
+
...documentCapabilities,
|
|
632
647
|
};
|
|
633
648
|
}
|
|
634
649
|
|
|
@@ -3,7 +3,7 @@ import process from 'node:process';
|
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { constants } from 'node:fs';
|
|
5
5
|
import { copyFile, link, lstat, mkdir, mkdtemp, open, readFile, readdir, rename, rm, stat, unlink, writeFile } from 'node:fs/promises';
|
|
6
|
-
import {
|
|
6
|
+
import { readDocxXmlText, readPdfText } from '../documents.mjs';
|
|
7
7
|
import { openDirectoryPath } from '../tools/files.mjs';
|
|
8
8
|
import { resolveSafePath, text } from '../util.mjs';
|
|
9
9
|
import {
|
|
@@ -353,6 +353,25 @@ async function readXlsx(filePath, args) {
|
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
+
async function readDocxDocument(filePath) {
|
|
357
|
+
const dir = await tempDir('remcp-docx-read-');
|
|
358
|
+
try {
|
|
359
|
+
await extractZip(filePath, dir);
|
|
360
|
+
let xml;
|
|
361
|
+
try {
|
|
362
|
+
xml = await readFile(path.join(dir, 'word', 'document.xml'), 'utf8');
|
|
363
|
+
} catch (error) {
|
|
364
|
+
if (error?.code === 'ENOENT') {
|
|
365
|
+
throw new Error('This file is not a readable .docx (word/document.xml is missing)');
|
|
366
|
+
}
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
return readDocxXmlText(xml);
|
|
370
|
+
} finally {
|
|
371
|
+
await removeTemp(dir);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
356
375
|
async function readPdfDocument(data) {
|
|
357
376
|
try {
|
|
358
377
|
return readPdfText(data);
|
|
@@ -380,10 +399,10 @@ async function readPdfDocument(data) {
|
|
|
380
399
|
export async function readDocument(args) {
|
|
381
400
|
const filePath = await resolveSafePath(args.path,'path');
|
|
382
401
|
const lower = filePath.toLowerCase();
|
|
402
|
+
if (lower.endsWith('.docx')) return text(await readDocxDocument(filePath));
|
|
403
|
+
if (lower.endsWith('.xlsx')) return jsonResult(await readXlsx(filePath,args));
|
|
383
404
|
const data = await readDocumentFile(filePath);
|
|
384
|
-
if (lower.endsWith('.docx')) return text(readDocxText(data));
|
|
385
405
|
if (lower.endsWith('.pdf')) return text(await readPdfDocument(data));
|
|
386
|
-
if (lower.endsWith('.xlsx')) return jsonResult(await readXlsx(filePath,args));
|
|
387
406
|
if (/\.(txt|md|csv|json|xml|yaml|yml)$/i.test(lower)) return text(data.toString('utf8'));
|
|
388
407
|
throw new Error('read_document supports PDF, DOCX, XLSX, TXT, Markdown, CSV, JSON, XML, YAML');
|
|
389
408
|
}
|
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?.();
|