@remcp/runtime 0.2.0
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 +40 -0
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/package.json +48 -0
- package/src/catalog.mjs +363 -0
- package/src/config.mjs +84 -0
- package/src/index.mjs +110 -0
- package/src/invoke.mjs +31 -0
- package/src/policy.mjs +55 -0
- package/src/sessions.mjs +219 -0
- package/src/telemetry.mjs +154 -0
- package/src/tools/files.mjs +239 -0
- package/src/tools/search.mjs +251 -0
- package/src/tools/stats.mjs +71 -0
- package/src/tools/system.mjs +64 -0
- package/src/tools/terminal.mjs +182 -0
- package/src/util.mjs +147 -0
- package/src/version.mjs +6 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
First feature-complete first-party release. ReMCP no longer needs to install an upstream MCP server
|
|
6
|
+
on a user's computer.
|
|
7
|
+
|
|
8
|
+
Tools (23, up from 19):
|
|
9
|
+
|
|
10
|
+
- add `copy_file` with an explicit `overwrite` flag;
|
|
11
|
+
- add `wait_for_process_output` so a model can wait for a pattern instead of polling
|
|
12
|
+
`read_process_output`;
|
|
13
|
+
- add read-only `get_runtime_info` and `get_runtime_stats`;
|
|
14
|
+
- `edit_block` falls back to whitespace-tolerant matching when the exact block is not found, reports
|
|
15
|
+
when it did, and still refuses ambiguous matches; `allow_fuzzy: false` keeps it strict.
|
|
16
|
+
|
|
17
|
+
Security:
|
|
18
|
+
|
|
19
|
+
- `allowedRoots` is now enforced against the resolved real path of the deepest existing ancestor.
|
|
20
|
+
Before this release a symlink inside an allowed root (`<allowed>/link -> /etc`) passed the lexical
|
|
21
|
+
prefix check and allowed reads, writes, searches, and listings outside the allowed directories.
|
|
22
|
+
- add a built-in catastrophic-command guardrail (`dangerousCommands: block|warn|allow`) covering
|
|
23
|
+
filesystem formatting, raw block-device writes, repartitioning, host power control, fork bombs,
|
|
24
|
+
root-path recursive deletion, root chmod/chown, history wiping, and Windows disk destruction;
|
|
25
|
+
- add `maxWriteBytes` so a single write cannot fill the disk through the relay;
|
|
26
|
+
- `kill_process` refuses the ReMCP agent process in addition to pid 1 and the runtime itself;
|
|
27
|
+
- blocked-command matching is case-insensitive and whitespace-normalized.
|
|
28
|
+
|
|
29
|
+
Usage metrics:
|
|
30
|
+
|
|
31
|
+
- opt-out metrics for tool names, durations, outcomes, and session counts, with a whitelisted event
|
|
32
|
+
schema that cannot carry paths, commands, arguments, or output;
|
|
33
|
+
- delivered as an MCP notification to the paired agent only - no telemetry endpoint, no install ping,
|
|
34
|
+
no postinstall script, no third-party processor, no remote feature flags;
|
|
35
|
+
- one-time notice on first run, `--describe` reports the state, `remcp telemetry off` disables it.
|
|
36
|
+
|
|
37
|
+
## 0.1.0
|
|
38
|
+
|
|
39
|
+
Initial runtime: 19 tools for files, search, terminal sessions, and processes, with one dependency
|
|
40
|
+
(`@modelcontextprotocol/sdk`), no postinstall script, and no network calls.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anton Baider
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# ReMCP local runtime
|
|
2
|
+
|
|
3
|
+
`@remcp/runtime` is the local device runtime for [ReMCP](https://remcp.delio24.com). It is an MCP
|
|
4
|
+
server that runs on a computer you paired with ReMCP and executes the file, search, terminal, and
|
|
5
|
+
process tools that the hosted ReMCP MCP endpoint exposes to ChatGPT and Codex.
|
|
6
|
+
|
|
7
|
+
The ReMCP device agent starts this runtime as a child process and talks to it over stdio. The runtime
|
|
8
|
+
never talks to the network on its own: it only answers the paired agent, which holds the device
|
|
9
|
+
credential you can revoke at any time.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
The runtime is installed automatically by the ReMCP device client:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g @remcp/remcp
|
|
17
|
+
remcp install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
To run it directly:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @remcp/runtime --describe # version, limits, configuration, telemetry state
|
|
24
|
+
npx @remcp/runtime --print-tools
|
|
25
|
+
npx @remcp/runtime # MCP server over stdio
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Tools
|
|
29
|
+
|
|
30
|
+
23 tools, all implemented in this repository.
|
|
31
|
+
|
|
32
|
+
| Tool | Behavior |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `read_file`, `read_multiple_files` | Read text files, with line paging and per-file errors in batch reads. |
|
|
35
|
+
| `list_directory`, `get_file_info` | Inspect directory contents and file metadata. |
|
|
36
|
+
| `write_file`, `edit_block` | Create, replace, or append file content; apply an exact-context text edit with a whitespace-tolerant fallback. |
|
|
37
|
+
| `create_directory`, `move_file`, `copy_file` | Create directories; move, rename, or copy without overwriting an existing path unless asked. |
|
|
38
|
+
| `start_search`, `get_more_search_results`, `stop_search`, `list_searches` | Streaming filename and content search with pagination, using `rg` when it is installed. |
|
|
39
|
+
| `start_process`, `read_process_output`, `wait_for_process_output`, `interact_with_process`, `force_terminate`, `list_sessions` | Run and drive terminal sessions, including REPLs, with pattern waits instead of polling. |
|
|
40
|
+
| `list_processes`, `kill_process` | Inspect and terminate operating-system processes. |
|
|
41
|
+
| `get_runtime_info`, `get_runtime_stats` | Read-only introspection of configuration, limits, guardrails, and local counters. |
|
|
42
|
+
|
|
43
|
+
There is deliberately no `set_config_value`: a model must not be able to rewrite its own device
|
|
44
|
+
limits. Configuration is file- and environment-based, owned by the person at the computer.
|
|
45
|
+
|
|
46
|
+
`--print-tools` prints the exact JSON contract (schemas and annotations) the runtime advertises, and
|
|
47
|
+
`src/catalog.mjs` is the single source of truth for it.
|
|
48
|
+
|
|
49
|
+
## Usage metrics
|
|
50
|
+
|
|
51
|
+
Usage metrics are **opt-out**, matching the ReMCP client. They cover tool names, durations, outcomes,
|
|
52
|
+
coarse error classes, and session counts. They never include file paths, file contents, command
|
|
53
|
+
strings, tool arguments, or tool output - the event schema is a whitelist, so a tool cannot leak
|
|
54
|
+
those fields even by accident.
|
|
55
|
+
|
|
56
|
+
Transport is the point that matters: the runtime has **no telemetry endpoint**. Events are emitted as
|
|
57
|
+
an MCP notification (`notifications/remcp/telemetry`) to the agent that started the runtime, and the
|
|
58
|
+
agent forwards them over the WebSocket connection it already holds to your own ReMCP account. There
|
|
59
|
+
is no install ping, no postinstall script, no third-party processor, no remote feature flags, and no
|
|
60
|
+
A/B assignment.
|
|
61
|
+
|
|
62
|
+
Turn it off in any of these ways:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
remcp telemetry off # the ReMCP client writes both config files
|
|
66
|
+
export REMCP_RUNTIME_DISABLE_TELEMETRY=1 # environment
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{ "telemetryEnabled": false }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
in `~/.config/remcp/runtime.json`. `--describe` always reports the current state.
|
|
74
|
+
|
|
75
|
+
## Safety model
|
|
76
|
+
|
|
77
|
+
- **No network calls.** The runtime opens no sockets. Every byte it emits goes to the paired agent.
|
|
78
|
+
- **Symlink-aware confinement.** `allowedRoots` is enforced against the resolved real path of the
|
|
79
|
+
deepest existing ancestor, not against the lexical string, so `<allowed>/link -> /etc` cannot be
|
|
80
|
+
used to read or write outside the allowed directories.
|
|
81
|
+
- **No silent overwrites.** `move_file` fails when the destination exists; `copy_file` requires
|
|
82
|
+
`overwrite: true`; `edit_block` fails unless the number of matched blocks equals
|
|
83
|
+
`expected_replacements`.
|
|
84
|
+
- **Catastrophic-command guardrail.** Commands that format filesystems, write raw block devices,
|
|
85
|
+
repartition disks, power off the host, fork-bomb, or recursively destroy a root path are refused
|
|
86
|
+
before they run (`dangerousCommands: block`, the default). `warn` runs them and reports the match;
|
|
87
|
+
`allow` disables the built-in list. User `blockedCommands` entries are always enforced.
|
|
88
|
+
- **Secret masking.** `list_processes` masks command arguments that look like tokens, passwords, or
|
|
89
|
+
API keys before returning them.
|
|
90
|
+
- **Bounded everything.** Tool results are capped (`maxOutputBytes`), writes are capped
|
|
91
|
+
(`maxWriteBytes`), buffered session output is capped (`maxBufferedLines`), and reads are paged.
|
|
92
|
+
- **Protected processes.** `kill_process` refuses pid 1, the runtime itself, and the ReMCP agent that
|
|
93
|
+
hosts it.
|
|
94
|
+
|
|
95
|
+
These are guardrails, not an operating-system sandbox. A user who can run a shell can reach anything
|
|
96
|
+
their account can reach; use a container, a VM, or a dedicated user account when that matters.
|
|
97
|
+
|
|
98
|
+
ReMCP device credentials are scoped to the paired computer and can be revoked from the ReMCP
|
|
99
|
+
workspace; revoking a device disconnects the agent that spawns this runtime.
|
|
100
|
+
|
|
101
|
+
## Configuration
|
|
102
|
+
|
|
103
|
+
Optional settings live in `~/.config/remcp/runtime.json` (override the directory with
|
|
104
|
+
`REMCP_RUNTIME_CONFIG_DIR`):
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"name": "workstation",
|
|
109
|
+
"allowedRoots": ["~/projects", "/srv/data"],
|
|
110
|
+
"blockedCommands": ["rm -rf /", "shutdown"],
|
|
111
|
+
"dangerousCommands": "block",
|
|
112
|
+
"telemetryEnabled": true,
|
|
113
|
+
"maxOutputBytes": 1048576,
|
|
114
|
+
"maxReadLines": 2000,
|
|
115
|
+
"maxBufferedLines": 50000,
|
|
116
|
+
"maxWriteBytes": 8388608,
|
|
117
|
+
"defaultShell": "/bin/bash"
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Every value can also be set with an environment variable: `REMCP_RUNTIME_ALLOWED_ROOTS`,
|
|
122
|
+
`REMCP_RUNTIME_BLOCKED_COMMANDS`, `REMCP_RUNTIME_DANGEROUS_COMMANDS`, `REMCP_RUNTIME_TELEMETRY`,
|
|
123
|
+
`REMCP_RUNTIME_DISABLE_TELEMETRY`, `REMCP_RUNTIME_MAX_OUTPUT_BYTES`, `REMCP_RUNTIME_MAX_READ_LINES`,
|
|
124
|
+
`REMCP_RUNTIME_MAX_BUFFERED_LINES`, `REMCP_RUNTIME_MAX_WRITE_BYTES`, `REMCP_RUNTIME_SHELL`,
|
|
125
|
+
`REMCP_RUNTIME_NAME`.
|
|
126
|
+
|
|
127
|
+
`allowedRoots` is empty by default, which means the paired device can reach anything the operating
|
|
128
|
+
system user running the agent can reach. Set it when you want the device to be scoped to specific
|
|
129
|
+
directories.
|
|
130
|
+
|
|
131
|
+
## Session behavior
|
|
132
|
+
|
|
133
|
+
Terminal sessions and searches live in memory for the lifetime of the runtime process. They end when
|
|
134
|
+
the agent restarts, and exited sessions are dropped 30 minutes after they finish.
|
|
135
|
+
|
|
136
|
+
## Development
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npm install
|
|
140
|
+
npm run check
|
|
141
|
+
npm test
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Relationship to other MCP servers
|
|
145
|
+
|
|
146
|
+
This runtime is an independent implementation written for ReMCP. It is not a fork of, and shares no
|
|
147
|
+
code with, Desktop Commander or any other MCP server. Compared with
|
|
148
|
+
[DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP) it keeps the same core
|
|
149
|
+
remote-computer workflow while dropping the parts ReMCP does not want on a user's machine: 34 runtime
|
|
150
|
+
dependencies (Supabase, Puppeteer/md-to-pdf, sharp, exceljs, Tiptap), the install-tracking postinstall
|
|
151
|
+
script, remote feature flags and A/B tests, unredacted local tool logs, URL fetching in `read_file`,
|
|
152
|
+
and `set_config_value`. What it adds is symlink-aware confinement, the catastrophic-command
|
|
153
|
+
guardrail, `copy_file`, pattern waits, whitespace-tolerant edits, and read-only introspection.
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT.
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@remcp/runtime",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "First-party ReMCP local device runtime: file, search, terminal and process tools over MCP for computers paired with ReMCP.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://remcp.delio24.com",
|
|
8
|
+
"author": "Anton Baider",
|
|
9
|
+
"bin": {
|
|
10
|
+
"remcp-runtime": "src/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"CHANGELOG.md"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.5.0"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"start": "node src/index.mjs",
|
|
23
|
+
"check": "node scripts/check.mjs",
|
|
24
|
+
"test": "node --test test/*.test.mjs"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/antonbaider/remcp-runtime.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/antonbaider/remcp/issues"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"mcp",
|
|
41
|
+
"model-context-protocol",
|
|
42
|
+
"remcp",
|
|
43
|
+
"remote",
|
|
44
|
+
"terminal",
|
|
45
|
+
"filesystem",
|
|
46
|
+
"self-hosted"
|
|
47
|
+
]
|
|
48
|
+
}
|
package/src/catalog.mjs
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { fileToolHandlers } from './tools/files.mjs';
|
|
2
|
+
import { searchToolHandlers } from './tools/search.mjs';
|
|
3
|
+
import { terminalToolHandlers } from './tools/terminal.mjs';
|
|
4
|
+
import { systemToolHandlers } from './tools/system.mjs';
|
|
5
|
+
import { statsToolHandlers } from './tools/stats.mjs';
|
|
6
|
+
|
|
7
|
+
const readOnly = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
8
|
+
const readOnlyNonIdempotent = { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
9
|
+
const additive = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
10
|
+
const mutating = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
11
|
+
const command = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true };
|
|
12
|
+
|
|
13
|
+
export const toolDefinitions = [
|
|
14
|
+
{
|
|
15
|
+
name: 'read_file',
|
|
16
|
+
title: 'Read file',
|
|
17
|
+
description: 'Read a text file on this computer. Use offset and length to page through large files; a negative offset reads from the end of the file.',
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
path: { type: 'string', description: 'Absolute path of the file to read. Relative paths resolve against the runtime working directory.' },
|
|
22
|
+
offset: { type: 'number', description: 'Zero-based first line to read. Negative values read the last N lines.' },
|
|
23
|
+
length: { type: 'number', description: 'Maximum number of lines to return.' },
|
|
24
|
+
},
|
|
25
|
+
required: ['path'],
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
},
|
|
28
|
+
annotations: readOnly,
|
|
29
|
+
handler: fileToolHandlers.read_file,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'read_multiple_files',
|
|
33
|
+
title: 'Read multiple files',
|
|
34
|
+
description: 'Read several text files in one call. Each file is returned separately and a failure to read one file does not stop the others.',
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
paths: { type: 'array', items: { type: 'string' }, description: 'Absolute paths of the files to read, at most 50 per call.' },
|
|
39
|
+
},
|
|
40
|
+
required: ['paths'],
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
},
|
|
43
|
+
annotations: readOnly,
|
|
44
|
+
handler: fileToolHandlers.read_multiple_files,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'list_directory',
|
|
48
|
+
title: 'List directory',
|
|
49
|
+
description: 'List the files and directories at a path. Entries are prefixed with [DIR], [FILE], or [LINK]; depth controls how many directory levels are included.',
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
path: { type: 'string', description: 'Absolute path of the directory to list.' },
|
|
54
|
+
depth: { type: 'number', description: 'Directory levels to list, from 1 to 5. Default 1.' },
|
|
55
|
+
},
|
|
56
|
+
required: ['path'],
|
|
57
|
+
additionalProperties: false,
|
|
58
|
+
},
|
|
59
|
+
annotations: readOnly,
|
|
60
|
+
handler: fileToolHandlers.list_directory,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'get_file_info',
|
|
64
|
+
title: 'Get file info',
|
|
65
|
+
description: 'Return metadata for a file or directory: type, size, timestamps, permissions, and for small text files the line count.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
path: { type: 'string', description: 'Absolute path to inspect.' },
|
|
70
|
+
},
|
|
71
|
+
required: ['path'],
|
|
72
|
+
additionalProperties: false,
|
|
73
|
+
},
|
|
74
|
+
annotations: readOnly,
|
|
75
|
+
handler: fileToolHandlers.get_file_info,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'write_file',
|
|
79
|
+
title: 'Write file',
|
|
80
|
+
description: 'Create a file or replace its full content. Parent directories are created automatically. Use mode "append" to add to the end instead of replacing the file.',
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
path: { type: 'string', description: 'Absolute path of the file to write.' },
|
|
85
|
+
content: { type: 'string', description: 'Full file content, or the text to append.' },
|
|
86
|
+
mode: { type: 'string', enum: ['rewrite', 'append'], description: 'rewrite replaces the file content, append adds to the end. Default rewrite.' },
|
|
87
|
+
},
|
|
88
|
+
required: ['path', 'content'],
|
|
89
|
+
additionalProperties: false,
|
|
90
|
+
},
|
|
91
|
+
annotations: mutating,
|
|
92
|
+
handler: fileToolHandlers.write_file,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'edit_block',
|
|
96
|
+
title: 'Edit file',
|
|
97
|
+
description: 'Replace an exact block of text in a file. Provide enough surrounding context to make old_string unique; the call fails unless the number of matches equals expected_replacements. When the exact text is not found, a whitespace-tolerant match is attempted and reported.',
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {
|
|
101
|
+
file_path: { type: 'string', description: 'Absolute path of the file to edit.' },
|
|
102
|
+
old_string: { type: 'string', description: 'Exact existing text to replace.' },
|
|
103
|
+
new_string: { type: 'string', description: 'Replacement text.' },
|
|
104
|
+
expected_replacements: { type: 'number', description: 'Number of matches required for the edit to apply. Default 1.' },
|
|
105
|
+
allow_fuzzy: { type: 'boolean', description: 'Allow a whitespace-tolerant fallback when the exact text is not found. Default true.' },
|
|
106
|
+
},
|
|
107
|
+
required: ['file_path', 'old_string', 'new_string'],
|
|
108
|
+
additionalProperties: false,
|
|
109
|
+
},
|
|
110
|
+
annotations: mutating,
|
|
111
|
+
handler: fileToolHandlers.edit_block,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'create_directory',
|
|
115
|
+
title: 'Create directory',
|
|
116
|
+
description: 'Create a directory, including any missing parent directories. Succeeds when the directory already exists.',
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
path: { type: 'string', description: 'Absolute path of the directory to create.' },
|
|
121
|
+
},
|
|
122
|
+
required: ['path'],
|
|
123
|
+
additionalProperties: false,
|
|
124
|
+
},
|
|
125
|
+
annotations: additive,
|
|
126
|
+
handler: fileToolHandlers.create_directory,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'move_file',
|
|
130
|
+
title: 'Move or rename',
|
|
131
|
+
description: 'Move or rename a file or directory. The call fails when the destination already exists, so nothing is overwritten.',
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
properties: {
|
|
135
|
+
source: { type: 'string', description: 'Absolute path to move.' },
|
|
136
|
+
destination: { type: 'string', description: 'Absolute destination path.' },
|
|
137
|
+
},
|
|
138
|
+
required: ['source', 'destination'],
|
|
139
|
+
additionalProperties: false,
|
|
140
|
+
},
|
|
141
|
+
annotations: additive,
|
|
142
|
+
handler: fileToolHandlers.move_file,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'copy_file',
|
|
146
|
+
title: 'Copy file',
|
|
147
|
+
description: 'Copy one file to a new path. The call fails when the destination exists unless overwrite is true. Directories are not copied recursively.',
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: 'object',
|
|
150
|
+
properties: {
|
|
151
|
+
source: { type: 'string', description: 'Absolute path of the file to copy.' },
|
|
152
|
+
destination: { type: 'string', description: 'Absolute destination path.' },
|
|
153
|
+
overwrite: { type: 'boolean', description: 'Replace the destination when it already exists. Default false.' },
|
|
154
|
+
},
|
|
155
|
+
required: ['source', 'destination'],
|
|
156
|
+
additionalProperties: false,
|
|
157
|
+
},
|
|
158
|
+
annotations: additive,
|
|
159
|
+
handler: fileToolHandlers.copy_file,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'start_search',
|
|
163
|
+
title: 'Start search',
|
|
164
|
+
description: 'Start a filename or content search on this computer and return the first results. Content searches return "path:line: text" rows. Use get_more_search_results to page and stop_search to stop a long search.',
|
|
165
|
+
inputSchema: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: {
|
|
168
|
+
path: { type: 'string', description: 'Absolute path of the directory or file to search.' },
|
|
169
|
+
pattern: { type: 'string', description: 'Regular expression for content searches, or a glob such as "*.ts" when searchType is files.' },
|
|
170
|
+
searchType: { type: 'string', enum: ['content', 'files'], description: 'Search file contents (default) or file names.' },
|
|
171
|
+
filePattern: { type: 'string', description: 'Optional relative glob that limits which files are searched, such as "*.ts".' },
|
|
172
|
+
ignoreCase: { type: 'boolean', description: 'Case-insensitive content matching.' },
|
|
173
|
+
maxResults: { type: 'number', description: 'Stop the search after this many results. Default 200.' },
|
|
174
|
+
includeHidden: { type: 'boolean', description: 'Include hidden files and directories.' },
|
|
175
|
+
includeIgnored: { type: 'boolean', description: 'Also search directories normally skipped, such as node_modules, dist, build, and virtualenvs.' },
|
|
176
|
+
contextLines: { type: 'number', description: 'Number of context lines to return around each content match, up to 10.' },
|
|
177
|
+
literalSearch: { type: 'boolean', description: 'Treat the pattern as literal text instead of a regular expression.' },
|
|
178
|
+
},
|
|
179
|
+
required: ['path', 'pattern'],
|
|
180
|
+
additionalProperties: false,
|
|
181
|
+
},
|
|
182
|
+
annotations: readOnlyNonIdempotent,
|
|
183
|
+
handler: searchToolHandlers.start_search,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'get_more_search_results',
|
|
187
|
+
title: 'Get more search results',
|
|
188
|
+
description: 'Read more results from a search started with start_search. Offset is zero-based; a negative offset returns the last N results.',
|
|
189
|
+
inputSchema: {
|
|
190
|
+
type: 'object',
|
|
191
|
+
properties: {
|
|
192
|
+
sessionId: { type: 'string', description: 'Search id returned by start_search.' },
|
|
193
|
+
offset: { type: 'number', description: 'Zero-based first result to return. Negative values read from the end.' },
|
|
194
|
+
length: { type: 'number', description: 'Maximum number of results to return. Default 100.' },
|
|
195
|
+
},
|
|
196
|
+
required: ['sessionId'],
|
|
197
|
+
additionalProperties: false,
|
|
198
|
+
},
|
|
199
|
+
annotations: readOnly,
|
|
200
|
+
handler: searchToolHandlers.get_more_search_results,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'stop_search',
|
|
204
|
+
title: 'Stop search',
|
|
205
|
+
description: 'Stop a running search. Results collected so far stay readable until the search is cleaned up.',
|
|
206
|
+
inputSchema: {
|
|
207
|
+
type: 'object',
|
|
208
|
+
properties: {
|
|
209
|
+
sessionId: { type: 'string', description: 'Search id returned by start_search.' },
|
|
210
|
+
},
|
|
211
|
+
required: ['sessionId'],
|
|
212
|
+
additionalProperties: false,
|
|
213
|
+
},
|
|
214
|
+
annotations: additive,
|
|
215
|
+
handler: searchToolHandlers.stop_search,
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: 'list_searches',
|
|
219
|
+
title: 'List searches',
|
|
220
|
+
description: 'List active and recent searches on this computer with their status and result counts.',
|
|
221
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
222
|
+
annotations: readOnly,
|
|
223
|
+
handler: searchToolHandlers.list_searches,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: 'start_process',
|
|
227
|
+
title: 'Start process',
|
|
228
|
+
description: 'Run a shell command on this computer and return its initial output. The process keeps running so read_process_output or interact_with_process can be used later. Commands can change local or external state.',
|
|
229
|
+
inputSchema: {
|
|
230
|
+
type: 'object',
|
|
231
|
+
properties: {
|
|
232
|
+
command: { type: 'string', description: 'Shell command to run.' },
|
|
233
|
+
timeout_ms: { type: 'number', description: 'How long to wait for initial output before returning, in milliseconds. Default 1000.' },
|
|
234
|
+
},
|
|
235
|
+
required: ['command'],
|
|
236
|
+
additionalProperties: false,
|
|
237
|
+
},
|
|
238
|
+
annotations: command,
|
|
239
|
+
handler: terminalToolHandlers.start_process,
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: 'read_process_output',
|
|
243
|
+
title: 'Read process output',
|
|
244
|
+
description: 'Read buffered output from a session started with start_process. Without an offset it returns output produced since the previous read.',
|
|
245
|
+
inputSchema: {
|
|
246
|
+
type: 'object',
|
|
247
|
+
properties: {
|
|
248
|
+
pid: { type: 'number', description: 'Session pid returned by start_process.' },
|
|
249
|
+
offset: { type: 'number', description: 'Zero-based line number to start from, counted across everything the session has produced; a negative value reads the last N lines. Omit for new output. Lines evicted by the buffer cap are no longer available.' },
|
|
250
|
+
length: { type: 'number', description: 'Maximum number of lines to return.' },
|
|
251
|
+
timeout_ms: { type: 'number', description: 'How long to wait for new output when no offset is given, in milliseconds.' },
|
|
252
|
+
},
|
|
253
|
+
required: ['pid'],
|
|
254
|
+
additionalProperties: false,
|
|
255
|
+
},
|
|
256
|
+
annotations: readOnly,
|
|
257
|
+
handler: terminalToolHandlers.read_process_output,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'interact_with_process',
|
|
261
|
+
title: 'Interact with process',
|
|
262
|
+
description: 'Send one line of input to a running session and return the output it produces. Use this for REPLs and other interactive commands.',
|
|
263
|
+
inputSchema: {
|
|
264
|
+
type: 'object',
|
|
265
|
+
properties: {
|
|
266
|
+
pid: { type: 'number', description: 'Session pid returned by start_process.' },
|
|
267
|
+
input: { type: 'string', description: 'Line of input to send; a newline is appended.' },
|
|
268
|
+
timeout_ms: { type: 'number', description: 'How long to wait for the response, in milliseconds. Default 1000.' },
|
|
269
|
+
},
|
|
270
|
+
required: ['pid', 'input'],
|
|
271
|
+
additionalProperties: false,
|
|
272
|
+
},
|
|
273
|
+
annotations: command,
|
|
274
|
+
handler: terminalToolHandlers.interact_with_process,
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: 'wait_for_process_output',
|
|
278
|
+
title: 'Wait for process output',
|
|
279
|
+
description: 'Wait until output from a running session matches a regular expression or literal string, then return the output collected since the previous read. Use this instead of polling read_process_output in a loop.',
|
|
280
|
+
inputSchema: {
|
|
281
|
+
type: 'object',
|
|
282
|
+
properties: {
|
|
283
|
+
pid: { type: 'number', description: 'Session pid returned by start_process.' },
|
|
284
|
+
pattern: { type: 'string', description: 'Regular expression or literal text to wait for.' },
|
|
285
|
+
timeout_ms: { type: 'number', description: 'How long to wait before returning the output collected so far, in milliseconds. Default 10000.' },
|
|
286
|
+
},
|
|
287
|
+
required: ['pid', 'pattern'],
|
|
288
|
+
additionalProperties: false,
|
|
289
|
+
},
|
|
290
|
+
annotations: readOnlyNonIdempotent,
|
|
291
|
+
handler: terminalToolHandlers.wait_for_process_output,
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: 'force_terminate',
|
|
295
|
+
title: 'Stop session',
|
|
296
|
+
description: 'Stop a session started with start_process, escalating from SIGTERM to SIGKILL when it does not exit.',
|
|
297
|
+
inputSchema: {
|
|
298
|
+
type: 'object',
|
|
299
|
+
properties: {
|
|
300
|
+
pid: { type: 'number', description: 'Session pid returned by start_process.' },
|
|
301
|
+
},
|
|
302
|
+
required: ['pid'],
|
|
303
|
+
additionalProperties: false,
|
|
304
|
+
},
|
|
305
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
306
|
+
handler: terminalToolHandlers.force_terminate,
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: 'list_sessions',
|
|
310
|
+
title: 'List sessions',
|
|
311
|
+
description: 'List terminal sessions started during this ReMCP runtime session with their status and how long they have been running.',
|
|
312
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
313
|
+
annotations: readOnly,
|
|
314
|
+
handler: terminalToolHandlers.list_sessions,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: 'list_processes',
|
|
318
|
+
title: 'List processes',
|
|
319
|
+
description: 'List running operating-system processes on this computer, highest CPU first, with pid, parent pid, CPU and memory usage, and command. Values that look like secrets are masked.',
|
|
320
|
+
inputSchema: {
|
|
321
|
+
type: 'object',
|
|
322
|
+
properties: {
|
|
323
|
+
limit: { type: 'number', description: 'Maximum number of processes to return. Default 100, maximum 1000.' },
|
|
324
|
+
},
|
|
325
|
+
additionalProperties: false,
|
|
326
|
+
},
|
|
327
|
+
annotations: readOnly,
|
|
328
|
+
handler: systemToolHandlers.list_processes,
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: 'kill_process',
|
|
332
|
+
title: 'Kill process',
|
|
333
|
+
description: 'Terminate an operating-system process by pid. Terminates the process and its children on Windows.',
|
|
334
|
+
inputSchema: {
|
|
335
|
+
type: 'object',
|
|
336
|
+
properties: {
|
|
337
|
+
pid: { type: 'number', description: 'Process id to terminate.' },
|
|
338
|
+
},
|
|
339
|
+
required: ['pid'],
|
|
340
|
+
additionalProperties: false,
|
|
341
|
+
},
|
|
342
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
343
|
+
handler: systemToolHandlers.kill_process,
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: 'get_runtime_info',
|
|
347
|
+
title: 'Get runtime info',
|
|
348
|
+
description: 'Report this device runtime: version, configuration, allowed roots, command policy, output limits, and whether usage metrics are enabled. Read-only; configuration cannot be changed through MCP.',
|
|
349
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
350
|
+
annotations: readOnly,
|
|
351
|
+
handler: statsToolHandlers.get_runtime_info,
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
name: 'get_runtime_stats',
|
|
355
|
+
title: 'Get runtime stats',
|
|
356
|
+
description: 'Report local counters for this runtime session: tool calls and failures, blocked commands, active terminal and search sessions, and usage-metric queue state.',
|
|
357
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
358
|
+
annotations: readOnly,
|
|
359
|
+
handler: statsToolHandlers.get_runtime_stats,
|
|
360
|
+
},
|
|
361
|
+
];
|
|
362
|
+
|
|
363
|
+
export const toolHandlers = new Map(toolDefinitions.map(definition => [definition.name, definition]));
|