@stratusagent/plugin-mcp 0.9.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/LICENSE +21 -0
- package/README.md +143 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +497 -0
- package/dist/index.js.map +1 -0
- package/dist/normalize.d.ts +57 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/normalize.js +161 -0
- package/dist/normalize.js.map +1 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stratus Labs
|
|
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,143 @@
|
|
|
1
|
+
# @stratusagent/plugin-mcp
|
|
2
|
+
|
|
3
|
+
The MCP bridge: mount any [Model Context Protocol](https://modelcontextprotocol.io)
|
|
4
|
+
server's tools as Stratus tools — under the same allowlists, the same risk
|
|
5
|
+
model, and the same approval policy as a tool written for Stratus. Every MCP
|
|
6
|
+
server anyone has already written becomes available to an agent whose soul
|
|
7
|
+
opts in, without that server's author knowing Stratus exists.
|
|
8
|
+
|
|
9
|
+
## Install and enable
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @stratusagent/plugin-mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```jsonc
|
|
16
|
+
// ~/.stratus/config.json — a trusted config only
|
|
17
|
+
{
|
|
18
|
+
"plugins": {
|
|
19
|
+
"@stratusagent/plugin-mcp": {
|
|
20
|
+
"enabled": true,
|
|
21
|
+
"servers": {
|
|
22
|
+
// A subprocess server, spoken to over stdio:
|
|
23
|
+
"linear": {
|
|
24
|
+
"command": "npx",
|
|
25
|
+
"args": ["-y", "@linear/mcp-server"],
|
|
26
|
+
"env": { "LINEAR_API_KEY": "lin_api_…" }
|
|
27
|
+
},
|
|
28
|
+
// A remote server, spoken to over Streamable HTTP:
|
|
29
|
+
"docs": {
|
|
30
|
+
"url": "https://mcp.example.com/mcp",
|
|
31
|
+
"headers": { "Authorization": "Bearer …" }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then allowlist the tools in a soul, exactly as for any other toolset:
|
|
40
|
+
|
|
41
|
+
```markdown
|
|
42
|
+
---
|
|
43
|
+
id: ava
|
|
44
|
+
tools: [mcp.linear.*, fs.read]
|
|
45
|
+
---
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Naming: `mcp.<server>.<tool>`
|
|
49
|
+
|
|
50
|
+
Tools register namespaced by the server's config key: the Linear server's
|
|
51
|
+
`create_issue` becomes `mcp.linear.create_issue`. A soul grants a whole
|
|
52
|
+
server with `mcp.linear.*` (or every bridged server with `mcp.*`), and an
|
|
53
|
+
operator reading a tool list sees at a glance which tools are somebody
|
|
54
|
+
else's code. Server keys become name segments, so they must be lowercase
|
|
55
|
+
`[a-z0-9_-]`; a discovered tool name is folded to the same shape
|
|
56
|
+
(`createIssue` → `mcp.linear.createissue`), and two names that fold to the
|
|
57
|
+
same segment refuse the server rather than letting one silently answer for
|
|
58
|
+
the other.
|
|
59
|
+
|
|
60
|
+
## Risk: ours, not the server's
|
|
61
|
+
|
|
62
|
+
MCP has no risk vocabulary, and a remote server's self-description is
|
|
63
|
+
exactly the input the trust model says not to take at face value. So
|
|
64
|
+
**every bridged tool registers `gated`** — a human decides when nobody is
|
|
65
|
+
watching — even when the server describes it as read-only. The package's
|
|
66
|
+
manifest declares the namespace (`toolsDiscovered: [{ "namespace": "mcp.*",
|
|
67
|
+
"risk": "gated" }]`) and the plugin host enforces it at registration, on
|
|
68
|
+
first connect and identically on reconnect.
|
|
69
|
+
|
|
70
|
+
The override is the operator's, explicit and per tool, through the host's
|
|
71
|
+
`toolRisks` key — sibling to `enabled`, applied by the host rather than by
|
|
72
|
+
this package's code:
|
|
73
|
+
|
|
74
|
+
```jsonc
|
|
75
|
+
"@stratusagent/plugin-mcp": {
|
|
76
|
+
"enabled": true,
|
|
77
|
+
"servers": { "linear": { … } },
|
|
78
|
+
"toolRisks": {
|
|
79
|
+
"mcp.linear.get_issue": "safe", // you read the server; it only reads
|
|
80
|
+
"mcp.linear.delete_issue": "dangerous"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Server settings
|
|
86
|
+
|
|
87
|
+
| Setting | Applies to | Meaning |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `command`, `args`, `cwd` | stdio | The subprocess to spawn. Exactly one of `command` / `url` per server. |
|
|
90
|
+
| `env` | stdio | Variables set outright, name to value — where a server's API key goes. |
|
|
91
|
+
| `passEnv` | stdio | Names forwarded from the daemon's environment. Defaults to `PATH`, `HOME`, `LANG`, `LC_ALL`, `TZ`. |
|
|
92
|
+
| `url` | HTTP | The Streamable HTTP endpoint. |
|
|
93
|
+
| `headers` | HTTP | Headers sent with every request — where a bearer token goes. |
|
|
94
|
+
| `connectTimeoutMs` | both | One budget for the connect handshake *and* the whole tool-discovery walk (default 15000), so a server can stall startup neither by being unreachable nor by paginating slowly. |
|
|
95
|
+
| `callTimeoutMs` | both | Per-call budget (default 60000). |
|
|
96
|
+
|
|
97
|
+
A setting on the wrong transport kind — `headers` on a stdio server, `env`
|
|
98
|
+
on an HTTP one — is refused at load rather than silently ignored, and so is
|
|
99
|
+
a `passEnv` that is not an array of names: a grant an operator believes is
|
|
100
|
+
in effect must never quietly be nothing.
|
|
101
|
+
|
|
102
|
+
**The stdio environment is replaced, not extended.** A subprocess MCP
|
|
103
|
+
server is a subprocess, and gets the same treatment `tool-shell`'s commands
|
|
104
|
+
do: exactly what this config granted — the harmless default inheritance,
|
|
105
|
+
`passEnv` names, `env` values — and nothing else. The daemon's environment,
|
|
106
|
+
where `ANTHROPIC_API_KEY` and every other exported key lives, is not there
|
|
107
|
+
to read.
|
|
108
|
+
|
|
109
|
+
OAuth-authenticated HTTP servers are not modeled yet; a static bearer in
|
|
110
|
+
`headers` is what exists today.
|
|
111
|
+
|
|
112
|
+
## Lifecycle
|
|
113
|
+
|
|
114
|
+
- **Connect at startup, degrade when down.** A server that is unreachable
|
|
115
|
+
leaves the daemon serving everything else, with a log line naming the
|
|
116
|
+
server and the config block to check; its tools appear when it comes back.
|
|
117
|
+
Reconnection backs off from one second to a minute.
|
|
118
|
+
- **Discovery is per connect.** Tool descriptors are cached from connect to
|
|
119
|
+
connect; a server that changes its list mid-run is picked up on
|
|
120
|
+
reconnect. A tool discovered only then registers normally; one no longer
|
|
121
|
+
advertised is unregistered — both logged, because an agent's allowlist
|
|
122
|
+
may no longer mean what it meant.
|
|
123
|
+
- **While a server is down**, calls to its tools fail with a "not
|
|
124
|
+
connected" error rather than hanging.
|
|
125
|
+
|
|
126
|
+
## Results
|
|
127
|
+
|
|
128
|
+
Tool results normalize into plain values:
|
|
129
|
+
|
|
130
|
+
- Text content comes back as a string (or under `text` when there is more).
|
|
131
|
+
- `structuredContent` passes through as `structured`.
|
|
132
|
+
- **Images, audio, and binary resources are written into the agent's
|
|
133
|
+
workspace** (`~/.stratus/workspaces/<agent>/mcp/<server>/`) and returned
|
|
134
|
+
under `files` — the key channels deliver as attachments, so an image from
|
|
135
|
+
a bridged tool reaches Slack like a screenshot does.
|
|
136
|
+
- Resource links pass through under `resources`.
|
|
137
|
+
- A result the server marks `isError` fails the call, like any failing tool.
|
|
138
|
+
|
|
139
|
+
## What this is not
|
|
140
|
+
|
|
141
|
+
Stratus does not become an MCP *server* here (exposing its tools to other
|
|
142
|
+
clients is a different step with a different threat model), and MCP prompts,
|
|
143
|
+
resources, and sampling are out of scope beyond what tool results need.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
2
|
+
import { type JsonObject, type Plugin } from '@stratusagent/core';
|
|
3
|
+
export { bridgedToolName, normalizeCallResult, sanitizeToolSegment, SERVER_NAME_PATTERN } from './normalize.ts';
|
|
4
|
+
/** What one configured server looks like once its block has been read. */
|
|
5
|
+
export interface McpServerSpec {
|
|
6
|
+
/** The config key — a name segment, so `mcp.<name>.*` is what a soul writes. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** stdio: the executable to spawn. Exactly one of `command` and `url`. */
|
|
9
|
+
command?: string;
|
|
10
|
+
args: string[];
|
|
11
|
+
cwd?: string;
|
|
12
|
+
/**
|
|
13
|
+
* The subprocess environment, already resolved: the scrubbed default
|
|
14
|
+
* inheritance plus whatever the operator granted by name or set
|
|
15
|
+
* outright. This is the whole environment — the daemon's is not there
|
|
16
|
+
* to read.
|
|
17
|
+
*/
|
|
18
|
+
env: Record<string, string>;
|
|
19
|
+
/** HTTP: the Streamable HTTP endpoint. */
|
|
20
|
+
url?: string;
|
|
21
|
+
/** HTTP: headers sent with every request — where a bearer token goes. */
|
|
22
|
+
headers: Record<string, string>;
|
|
23
|
+
connectTimeoutMs: number;
|
|
24
|
+
callTimeoutMs: number;
|
|
25
|
+
}
|
|
26
|
+
export interface McpPluginOptions {
|
|
27
|
+
/** The environment `passEnv` grants *from*. Defaults to the daemon's. */
|
|
28
|
+
processEnv?: NodeJS.ProcessEnv;
|
|
29
|
+
/**
|
|
30
|
+
* Transport seam for tests: hand back a connected pair instead of
|
|
31
|
+
* spawning or dialing. The default builds stdio or Streamable HTTP from
|
|
32
|
+
* the spec.
|
|
33
|
+
*/
|
|
34
|
+
transportFor?: (spec: McpServerSpec) => Transport | Promise<Transport>;
|
|
35
|
+
/**
|
|
36
|
+
* Reconnect pacing, attempt (0-based) to delay. The default doubles from
|
|
37
|
+
* one second and caps at one minute; tests shrink it so a reconnect is
|
|
38
|
+
* an event to await rather than a wall-clock to sleep through.
|
|
39
|
+
*/
|
|
40
|
+
reconnectDelayMs?: (attempt: number) => number;
|
|
41
|
+
/**
|
|
42
|
+
* Called after a server (re)connects and its tools are registered — the
|
|
43
|
+
* gate a test holds instead of guessing at timers, and a hook a host
|
|
44
|
+
* could count connections with.
|
|
45
|
+
*/
|
|
46
|
+
onConnected?: (server: string) => void;
|
|
47
|
+
log?: (message: string) => void;
|
|
48
|
+
warn?: (message: string) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Clock seam for the discovery deadline: the budget arithmetic only shows
|
|
51
|
+
* itself when time passes mid-walk, which a real clock makes a race.
|
|
52
|
+
*/
|
|
53
|
+
now?: () => number;
|
|
54
|
+
}
|
|
55
|
+
export declare const createMcpPlugin: (config?: JsonObject, options?: McpPluginOptions) => Plugin;
|
|
56
|
+
/** The loader's ABI. See `docs/architecture/plugins.md`. */
|
|
57
|
+
export declare const createPlugin: (config: JsonObject) => Plugin;
|
|
58
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAE/E,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,MAAM,EAIZ,MAAM,oBAAoB,CAAC;AAS5B,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAiChH,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAC/B;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACvE;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AA+LD,eAAO,MAAM,eAAe,GAAI,SAAQ,UAAe,EAAE,UAAS,gBAAqB,KAAG,MAkWzF,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,YAAY,GAAI,QAAQ,UAAU,KAAG,MAAiC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
|
+
import { StreamableHTTPClientTransport, StreamableHTTPError } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
|
+
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { DEFAULT_SUBPROCESS_PASS_ENV, } from '@stratusagent/core';
|
|
6
|
+
import { bridgedToolName, normalizeCallResult, sanitizeToolSegment, SERVER_NAME_PATTERN, } from "./normalize.js";
|
|
7
|
+
export { bridgedToolName, normalizeCallResult, sanitizeToolSegment, SERVER_NAME_PATTERN } from "./normalize.js";
|
|
8
|
+
/**
|
|
9
|
+
* The MCP bridge: mounts Model Context Protocol servers as Stratus tools.
|
|
10
|
+
*
|
|
11
|
+
* Each configured server's tools register as `mcp.<server>.<tool>`, so a
|
|
12
|
+
* soul allowlists `mcp.linear.*` and an operator can see at a glance which
|
|
13
|
+
* tools are somebody else's code. Risk is ours, not the server's: the
|
|
14
|
+
* manifest declares the `mcp.*` namespace `gated`, the registration view
|
|
15
|
+
* enforces it, and an operator who has read a server lowers a specific
|
|
16
|
+
* tool through the host's `toolRisks` key — never through anything this
|
|
17
|
+
* package could do on its own.
|
|
18
|
+
*
|
|
19
|
+
* Lifecycle: connect at startup, reconnect with backoff, and a server that
|
|
20
|
+
* is down degrades to its tools being unavailable rather than failing the
|
|
21
|
+
* daemon's start. Tool descriptors are built once per connect; a server
|
|
22
|
+
* that changes its list mid-run is picked up on reconnect, and the change
|
|
23
|
+
* is logged because an agent's allowlist may no longer mean what it meant.
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 15_000;
|
|
26
|
+
const DEFAULT_CALL_TIMEOUT_MS = 60_000;
|
|
27
|
+
const RECONNECT_INITIAL_DELAY_MS = 1_000;
|
|
28
|
+
const RECONNECT_MAX_DELAY_MS = 60_000;
|
|
29
|
+
/**
|
|
30
|
+
* How many pages of `tools/list` discovery will follow. The per-request
|
|
31
|
+
* timeout bounds each page, not the walk — a server that always answers
|
|
32
|
+
* with a nextCursor would otherwise hold `setup()`, and with it the
|
|
33
|
+
* daemon's start, forever. Which is exactly the kind of say-so this
|
|
34
|
+
* package's trust model exists to not extend to a remote server.
|
|
35
|
+
*/
|
|
36
|
+
const MAX_TOOL_LIST_PAGES = 100;
|
|
37
|
+
class McpConfigError extends Error {
|
|
38
|
+
constructor(message) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = 'McpConfigError';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
44
|
+
const asStringRecord = (value, where) => {
|
|
45
|
+
if (value === undefined) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
if (!isObject(value)) {
|
|
49
|
+
throw new McpConfigError(`${where} must be an object of string values.`);
|
|
50
|
+
}
|
|
51
|
+
const record = {};
|
|
52
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
53
|
+
if (typeof entry !== 'string') {
|
|
54
|
+
throw new McpConfigError(`${where}.${key} must be a string.`);
|
|
55
|
+
}
|
|
56
|
+
record[key] = entry;
|
|
57
|
+
}
|
|
58
|
+
return record;
|
|
59
|
+
};
|
|
60
|
+
const asTimeout = (value, fallback) => typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
61
|
+
const asStringArray = (value, where) => {
|
|
62
|
+
if (value === undefined) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string')) {
|
|
66
|
+
throw new McpConfigError(`${where} must be an array of strings.`);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Resolve one server block into a spec, or refuse it. Refusals here are
|
|
72
|
+
* configuration errors and fail the plugin at load — a mistyped block is
|
|
73
|
+
* the operator's to fix, unlike a server that is merely down.
|
|
74
|
+
*/
|
|
75
|
+
const resolveServerSpec = (name, block, processEnv) => {
|
|
76
|
+
const where = `servers.${name}`;
|
|
77
|
+
if (!SERVER_NAME_PATTERN.test(name)) {
|
|
78
|
+
throw new McpConfigError(`Server name ${JSON.stringify(name)} is not usable: it becomes a tool-name segment (mcp.${name}.*), so it must be lowercase [a-z0-9_-] starting with a letter or digit.`);
|
|
79
|
+
}
|
|
80
|
+
if (!isObject(block)) {
|
|
81
|
+
throw new McpConfigError(`${where} must be an object.`);
|
|
82
|
+
}
|
|
83
|
+
const command = typeof block.command === 'string' && block.command.length > 0 ? block.command : undefined;
|
|
84
|
+
const url = typeof block.url === 'string' && block.url.length > 0 ? block.url : undefined;
|
|
85
|
+
if ((command === undefined) === (url === undefined)) {
|
|
86
|
+
throw new McpConfigError(`${where} must set exactly one of "command" (stdio) or "url" (HTTP).`);
|
|
87
|
+
}
|
|
88
|
+
if (url !== undefined) {
|
|
89
|
+
try {
|
|
90
|
+
void new URL(url);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw new McpConfigError(`${where}.url is not a URL: ${url}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// A setting on the wrong transport kind is refused, not quietly unused:
|
|
97
|
+
// an operator who wrote `headers` for a server that turned out to be
|
|
98
|
+
// stdio believes a bearer token is being sent, and the mistyped grant
|
|
99
|
+
// that silently does nothing is the failure mode the toolRisks parser
|
|
100
|
+
// already refuses by design.
|
|
101
|
+
for (const key of ['args', 'cwd', 'env', 'passEnv']) {
|
|
102
|
+
if (url !== undefined && block[key] !== undefined) {
|
|
103
|
+
throw new McpConfigError(`${where}.${key} only applies to a stdio server ("command"); this server sets "url".`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (command !== undefined && block.headers !== undefined) {
|
|
107
|
+
throw new McpConfigError(`${where}.headers only applies to an HTTP server ("url"); this server sets "command".`);
|
|
108
|
+
}
|
|
109
|
+
// The replacement-environment treatment: the child gets exactly what was
|
|
110
|
+
// granted — the harmless default inheritance, names the operator
|
|
111
|
+
// forwarded, values the operator set — and nothing else. The daemon's
|
|
112
|
+
// environment holds every key an operator exported, and a subprocess MCP
|
|
113
|
+
// server is a subprocess: ANTHROPIC_API_KEY must not be there to read.
|
|
114
|
+
const env = {};
|
|
115
|
+
const passEnv = asStringArray(block.passEnv, `${where}.passEnv`) ?? [...DEFAULT_SUBPROCESS_PASS_ENV];
|
|
116
|
+
for (const key of passEnv) {
|
|
117
|
+
const value = processEnv[key];
|
|
118
|
+
if (value !== undefined) {
|
|
119
|
+
env[key] = value;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
Object.assign(env, asStringRecord(block.env, `${where}.env`));
|
|
123
|
+
return {
|
|
124
|
+
name,
|
|
125
|
+
...(command !== undefined ? { command } : {}),
|
|
126
|
+
args: asStringArray(block.args, `${where}.args`) ?? [],
|
|
127
|
+
...(typeof block.cwd === 'string' && block.cwd.length > 0 ? { cwd: block.cwd } : {}),
|
|
128
|
+
env,
|
|
129
|
+
...(url !== undefined ? { url } : {}),
|
|
130
|
+
headers: asStringRecord(block.headers, `${where}.headers`),
|
|
131
|
+
connectTimeoutMs: asTimeout(block.connectTimeoutMs, DEFAULT_CONNECT_TIMEOUT_MS),
|
|
132
|
+
callTimeoutMs: asTimeout(block.callTimeoutMs, DEFAULT_CALL_TIMEOUT_MS),
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Whether a failed call means the *connection* failed, as opposed to the
|
|
137
|
+
* tool. A stdio server dying fires `client.onclose`, but a Streamable HTTP
|
|
138
|
+
* server that restarted or expired its session reports the loss only on
|
|
139
|
+
* the request path — the stale `mcp-session-id` gets an HTTP error, and
|
|
140
|
+
* nothing ever closes the transport — so without classifying rejections
|
|
141
|
+
* here, every later call would keep reusing the dead session until the
|
|
142
|
+
* daemon restarted.
|
|
143
|
+
*
|
|
144
|
+
* - `StreamableHTTPError` is the transport's own word for an HTTP-level
|
|
145
|
+
* failure (a 404 on a stale session, a 5xx).
|
|
146
|
+
* - `ErrorCode.ConnectionClosed` is a request rejected because the
|
|
147
|
+
* transport went away under it.
|
|
148
|
+
* - A `TypeError` is how `fetch` reports a network failure (the server is
|
|
149
|
+
* simply gone); no JSON-RPC error from a live server arrives as one.
|
|
150
|
+
*
|
|
151
|
+
* Everything else — the server answering with a tool error, invalid
|
|
152
|
+
* params, a timeout on a slow tool — is a live connection and no reason
|
|
153
|
+
* to tear it down.
|
|
154
|
+
*/
|
|
155
|
+
const isConnectionFailure = (error) => error instanceof StreamableHTTPError
|
|
156
|
+
|| (error instanceof McpError && error.code === ErrorCode.ConnectionClosed)
|
|
157
|
+
|| error instanceof TypeError;
|
|
158
|
+
const buildTransport = (spec) => {
|
|
159
|
+
if (spec.command !== undefined) {
|
|
160
|
+
return new StdioClientTransport({
|
|
161
|
+
command: spec.command,
|
|
162
|
+
args: spec.args,
|
|
163
|
+
...(spec.cwd !== undefined ? { cwd: spec.cwd } : {}),
|
|
164
|
+
env: spec.env,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// The assertion papers over the SDK's own optional-property types not
|
|
168
|
+
// being written for exactOptionalPropertyTypes; the object is a Transport.
|
|
169
|
+
return new StreamableHTTPClientTransport(new URL(spec.url), {
|
|
170
|
+
requestInit: { headers: spec.headers },
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
export const createMcpPlugin = (config = {}, options = {}) => {
|
|
174
|
+
const log = options.log ?? ((message) => console.error(`[plugin-mcp] ${message}`));
|
|
175
|
+
const warn = options.warn ?? ((message) => console.error(`[plugin-mcp] ${message}`));
|
|
176
|
+
const processEnv = options.processEnv ?? process.env;
|
|
177
|
+
const delayFor = options.reconnectDelayMs
|
|
178
|
+
?? ((attempt) => Math.min(RECONNECT_INITIAL_DELAY_MS * 2 ** attempt, RECONNECT_MAX_DELAY_MS));
|
|
179
|
+
const workspaceRoot = typeof config.workspaceRoot === 'string' ? config.workspaceRoot : undefined;
|
|
180
|
+
if (!isObject(config.servers)) {
|
|
181
|
+
throw new McpConfigError('plugins["@stratusagent/plugin-mcp"] needs a "servers" object — one entry per MCP server, keyed by the name its tools mount under.');
|
|
182
|
+
}
|
|
183
|
+
const states = Object.entries(config.servers).map(([name, block]) => ({
|
|
184
|
+
spec: resolveServerSpec(name, block, processEnv),
|
|
185
|
+
client: undefined,
|
|
186
|
+
pending: undefined,
|
|
187
|
+
connected: false,
|
|
188
|
+
connecting: false,
|
|
189
|
+
attempt: 0,
|
|
190
|
+
timer: undefined,
|
|
191
|
+
tools: new Map(),
|
|
192
|
+
}));
|
|
193
|
+
let view;
|
|
194
|
+
let disposed = false;
|
|
195
|
+
const proxyTool = (state, registeredName, info) => ({
|
|
196
|
+
name: registeredName,
|
|
197
|
+
description: info.description ?? `Tool ${info.mcpName} on the MCP server ${state.spec.name}.`,
|
|
198
|
+
...(info.parameters ? { parameters: info.parameters } : {}),
|
|
199
|
+
// No risk claim, deliberately: what a bridged tool registers at is the
|
|
200
|
+
// namespace's declared risk (and any operator override), applied by
|
|
201
|
+
// the manifest-bound view — the server's opinion of itself never
|
|
202
|
+
// enters, and neither does this package's.
|
|
203
|
+
async execute(input, session, context) {
|
|
204
|
+
const client = state.connected ? state.client : undefined;
|
|
205
|
+
if (!client) {
|
|
206
|
+
throw new Error(`MCP server ${state.spec.name} is not connected; ${registeredName} is unavailable until it comes back.`);
|
|
207
|
+
}
|
|
208
|
+
// Identity, not equality, against the live map: syncTools keeps the
|
|
209
|
+
// same descriptor object across a reconnect exactly when nothing
|
|
210
|
+
// about the tool changed. A call can be held between issue and
|
|
211
|
+
// execution — parked on a human's approval — and a reconnect that
|
|
212
|
+
// swapped the definition underneath it must fail the call rather
|
|
213
|
+
// than run the replacement with input approved for the original.
|
|
214
|
+
if (state.tools.get(registeredName) !== info) {
|
|
215
|
+
throw new Error(`${registeredName} changed on MCP server ${state.spec.name} after this call was issued — the server now advertises a different definition, or none. Call it again to run the updated tool.`);
|
|
216
|
+
}
|
|
217
|
+
let result;
|
|
218
|
+
try {
|
|
219
|
+
result = await client.callTool({ name: info.mcpName, arguments: input }, undefined, {
|
|
220
|
+
timeout: state.spec.callTimeoutMs,
|
|
221
|
+
...(context?.signal ? { signal: context.signal } : {}),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
// A session-loss surfaces here, not on onclose — see
|
|
226
|
+
// isConnectionFailure. Guarded on the client this call used, so a
|
|
227
|
+
// reconnect that already replaced it is left alone.
|
|
228
|
+
if (isConnectionFailure(error) && state.client === client) {
|
|
229
|
+
dropConnection(state, error);
|
|
230
|
+
}
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
return normalizeCallResult(result, {
|
|
234
|
+
server: state.spec.name,
|
|
235
|
+
tool: info.mcpName,
|
|
236
|
+
agentId: session.agent.id,
|
|
237
|
+
...(workspaceRoot !== undefined ? { workspaceRoot } : {}),
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
/** Every page of `tools/list`, not just the first. */
|
|
242
|
+
const listAllTools = async (client, spec, remainingMs) => {
|
|
243
|
+
const tools = [];
|
|
244
|
+
let cursor;
|
|
245
|
+
let pages = 0;
|
|
246
|
+
do {
|
|
247
|
+
pages += 1;
|
|
248
|
+
if (pages > MAX_TOOL_LIST_PAGES) {
|
|
249
|
+
throw new Error(`MCP server ${spec.name} returned more than ${MAX_TOOL_LIST_PAGES} pages of tools/list; refusing discovery rather than following its cursors forever.`);
|
|
250
|
+
}
|
|
251
|
+
// One budget for the whole walk, not one per page: a fresh timeout
|
|
252
|
+
// for every request would let a slowly paginating server multiply
|
|
253
|
+
// the advertised connect bound by its page count, holding setup —
|
|
254
|
+
// and daemon start — for minutes on end.
|
|
255
|
+
const budget = remainingMs();
|
|
256
|
+
if (budget <= 0) {
|
|
257
|
+
throw new Error(`MCP server ${spec.name} did not finish tool discovery within its ${spec.connectTimeoutMs}ms connect budget.`);
|
|
258
|
+
}
|
|
259
|
+
// A cursor is the server's opaque string, the empty string included —
|
|
260
|
+
// dropped on truthiness, an "" cursor would refetch the first page
|
|
261
|
+
// until the page guard condemned a compliant server.
|
|
262
|
+
const page = await client.listTools(cursor !== undefined ? { cursor } : undefined, { timeout: budget });
|
|
263
|
+
tools.push(...page.tools);
|
|
264
|
+
cursor = page.nextCursor;
|
|
265
|
+
} while (cursor !== undefined);
|
|
266
|
+
return tools;
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* Reconcile what a server advertises with what is registered — first
|
|
270
|
+
* connect and reconnect run the identical path, through the identical
|
|
271
|
+
* manifest-bound gate. Every difference is logged: a tool appearing,
|
|
272
|
+
* disappearing, or changing shape changes what an agent's allowlist
|
|
273
|
+
* selects.
|
|
274
|
+
*/
|
|
275
|
+
const syncTools = (state, advertised, firstConnect) => {
|
|
276
|
+
if (!view) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const next = new Map();
|
|
280
|
+
for (const tool of advertised) {
|
|
281
|
+
const segment = sanitizeToolSegment(tool.name);
|
|
282
|
+
if (segment === undefined) {
|
|
283
|
+
throw new McpConfigError(`MCP server ${state.spec.name} advertises a tool named ${JSON.stringify(tool.name)}, which leaves nothing usable as a name segment.`);
|
|
284
|
+
}
|
|
285
|
+
const registered = bridgedToolName(state.spec.name, segment);
|
|
286
|
+
if (next.has(registered)) {
|
|
287
|
+
// Folding two of the server's names into one registered name would
|
|
288
|
+
// make the second silently answer calls meant for the first —
|
|
289
|
+
// refused, the same reason any collision is.
|
|
290
|
+
throw new McpConfigError(`MCP server ${state.spec.name} advertises two tools that both bridge to ${registered} (one of them ${JSON.stringify(tool.name)}). Rename one on the server.`);
|
|
291
|
+
}
|
|
292
|
+
next.set(registered, {
|
|
293
|
+
mcpName: tool.name,
|
|
294
|
+
...(typeof tool.description === 'string' ? { description: tool.description } : {}),
|
|
295
|
+
...(isObject(tool.inputSchema) ? { parameters: tool.inputSchema } : {}),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
for (const registered of state.tools.keys()) {
|
|
299
|
+
if (!next.has(registered)) {
|
|
300
|
+
view.unregister(registered);
|
|
301
|
+
log(`mcp server ${state.spec.name} no longer advertises ${registered} — unregistered; souls allowlisting it select nothing until it returns`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
for (const [registered, info] of next) {
|
|
305
|
+
const existing = state.tools.get(registered);
|
|
306
|
+
const changed = existing !== undefined
|
|
307
|
+
&& (existing.mcpName !== info.mcpName
|
|
308
|
+
|| existing.description !== info.description
|
|
309
|
+
|| JSON.stringify(existing.parameters) !== JSON.stringify(info.parameters));
|
|
310
|
+
if (existing !== undefined && !changed) {
|
|
311
|
+
// The identical descriptor keeps its identity across the
|
|
312
|
+
// reconnect, on purpose: the proxy's staleness check compares the
|
|
313
|
+
// object it captured against this map, and only an actual change
|
|
314
|
+
// may fail a call that was issued — or approved — before it.
|
|
315
|
+
next.set(registered, existing);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (changed) {
|
|
319
|
+
view.unregister(registered);
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
view.register(proxyTool(state, registered, info));
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
// On first connect this is a load-time refusal and propagates; on
|
|
326
|
+
// reconnect there is no load to refuse, so the one colliding tool
|
|
327
|
+
// is skipped, named, and everything else keeps working.
|
|
328
|
+
if (firstConnect) {
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
next.delete(registered);
|
|
332
|
+
warn(`mcp server ${state.spec.name}: ${registered} was not registered: ${error instanceof Error ? error.message : String(error)}`);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (!firstConnect) {
|
|
336
|
+
log(`mcp server ${state.spec.name} ${changed ? 'changed' : 'added'} ${registered}${changed ? '' : ' — an allowlist naming mcp.' + state.spec.name + '.* now grants it'}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
state.tools = next;
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* Treat a server whose connection failed mid-call as down: further calls
|
|
343
|
+
* refuse with "not connected" instead of re-sending into a dead session,
|
|
344
|
+
* and the reconnect loop brings it back. The onclose handler is keyed to
|
|
345
|
+
* `state.client`, already cleared here, so closing cannot schedule a
|
|
346
|
+
* second reconnect.
|
|
347
|
+
*/
|
|
348
|
+
const dropConnection = (state, cause) => {
|
|
349
|
+
const client = state.client;
|
|
350
|
+
if (!client || disposed) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
state.client = undefined;
|
|
354
|
+
state.connected = false;
|
|
355
|
+
warn(`mcp server ${state.spec.name} connection failed mid-call — its tools are unavailable until it reconnects: `
|
|
356
|
+
+ `${cause instanceof Error ? cause.message : String(cause)}`);
|
|
357
|
+
void client.close().catch(() => { });
|
|
358
|
+
scheduleReconnect(state);
|
|
359
|
+
};
|
|
360
|
+
const scheduleReconnect = (state) => {
|
|
361
|
+
if (disposed || state.timer !== undefined || state.connecting) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const delay = delayFor(state.attempt);
|
|
365
|
+
state.attempt += 1;
|
|
366
|
+
state.timer = setTimeout(() => {
|
|
367
|
+
state.timer = undefined;
|
|
368
|
+
void connect(state, false).catch((error) => {
|
|
369
|
+
warn(`mcp server ${state.spec.name} reconnect failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
370
|
+
scheduleReconnect(state);
|
|
371
|
+
});
|
|
372
|
+
}, delay);
|
|
373
|
+
// A daemon holds the process open anyway; a one-shot CLI run must not
|
|
374
|
+
// be kept alive by a server that is never coming back.
|
|
375
|
+
state.timer.unref?.();
|
|
376
|
+
};
|
|
377
|
+
const connect = async (state, firstConnect) => {
|
|
378
|
+
if (disposed || state.connecting) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
state.connecting = true;
|
|
382
|
+
try {
|
|
383
|
+
const transport = await (options.transportFor ?? buildTransport)(state.spec);
|
|
384
|
+
// `disposed` is re-checked after every await from here on: dispose()
|
|
385
|
+
// closes state.client, and a connect still in flight when it runs —
|
|
386
|
+
// one server's load failing while another is mid-handshake — would
|
|
387
|
+
// otherwise finish afterwards and strand a connected client, which
|
|
388
|
+
// for a stdio server is an orphaned subprocess holding granted env.
|
|
389
|
+
if (disposed) {
|
|
390
|
+
await transport.close().catch(() => { });
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
// Tracked from here until the connect settles (the finally below),
|
|
394
|
+
// so dispose() can cut a handshake short instead of waiting for
|
|
395
|
+
// these awaits to notice `disposed` on their own.
|
|
396
|
+
state.pending = transport;
|
|
397
|
+
const client = new Client({ name: '@stratusagent/plugin-mcp', version: '0.9.0' });
|
|
398
|
+
// A close that lands before this client is published cannot be
|
|
399
|
+
// dropped: the identity guard below would discard it, and connect()
|
|
400
|
+
// would then mark an already-closed client connected — leaving the
|
|
401
|
+
// first tool call to fail for nothing and recovery to the call-path
|
|
402
|
+
// classifier.
|
|
403
|
+
let closedBeforePublish = false;
|
|
404
|
+
client.onclose = () => {
|
|
405
|
+
if (disposed) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (state.client !== client) {
|
|
409
|
+
closedBeforePublish = true;
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
state.connected = false;
|
|
413
|
+
state.client = undefined;
|
|
414
|
+
warn(`mcp server ${state.spec.name} disconnected — its tools are unavailable until it comes back`);
|
|
415
|
+
scheduleReconnect(state);
|
|
416
|
+
};
|
|
417
|
+
// One deadline for the handshake and the whole discovery walk — see
|
|
418
|
+
// listAllTools for why the walk must not get a fresh budget per page.
|
|
419
|
+
const clock = options.now ?? Date.now;
|
|
420
|
+
const deadline = clock() + state.spec.connectTimeoutMs;
|
|
421
|
+
await client.connect(transport, { timeout: state.spec.connectTimeoutMs });
|
|
422
|
+
try {
|
|
423
|
+
const advertised = await listAllTools(client, state.spec, () => deadline - clock());
|
|
424
|
+
if (disposed) {
|
|
425
|
+
throw new Error(`disposed while discovering ${state.spec.name}`);
|
|
426
|
+
}
|
|
427
|
+
if (closedBeforePublish) {
|
|
428
|
+
throw new Error(`MCP server ${state.spec.name} closed the connection during discovery.`);
|
|
429
|
+
}
|
|
430
|
+
syncTools(state, advertised, firstConnect);
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
// The client is connected but not yet stored, so nothing else will
|
|
434
|
+
// ever close it — for a stdio server that is a spawned subprocess
|
|
435
|
+
// left running. Let go before reporting the failure.
|
|
436
|
+
await client.close().catch(() => { });
|
|
437
|
+
throw error;
|
|
438
|
+
}
|
|
439
|
+
state.client = client;
|
|
440
|
+
state.connected = true;
|
|
441
|
+
state.attempt = 0;
|
|
442
|
+
log(`mcp server ${state.spec.name} connected — ${state.tools.size} tool${state.tools.size === 1 ? '' : 's'} under mcp.${state.spec.name}.*`);
|
|
443
|
+
options.onConnected?.(state.spec.name);
|
|
444
|
+
}
|
|
445
|
+
finally {
|
|
446
|
+
state.connecting = false;
|
|
447
|
+
state.pending = undefined;
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
return {
|
|
451
|
+
name: '@stratusagent/plugin-mcp',
|
|
452
|
+
async setup(context) {
|
|
453
|
+
view = context.tools;
|
|
454
|
+
await Promise.all(states.map(async (state) => {
|
|
455
|
+
try {
|
|
456
|
+
await connect(state, true);
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
// A configuration or trust refusal fails the plugin at load; a
|
|
460
|
+
// server that is merely unreachable must not — the daemon goes
|
|
461
|
+
// on serving every other agent, and this server's tools appear
|
|
462
|
+
// when it does.
|
|
463
|
+
if (error instanceof McpConfigError || error?.name === 'PluginManifestError') {
|
|
464
|
+
throw error;
|
|
465
|
+
}
|
|
466
|
+
warn(`mcp server ${state.spec.name} is unreachable — starting without its tools; they register when it comes back. `
|
|
467
|
+
+ `Check ${state.spec.command !== undefined ? `the command (${state.spec.command})` : `the endpoint (${state.spec.url})`} `
|
|
468
|
+
+ `under plugins["@stratusagent/plugin-mcp"].servers.${state.spec.name}. `
|
|
469
|
+
+ `(${error instanceof Error ? error.message : String(error)})`);
|
|
470
|
+
scheduleReconnect(state);
|
|
471
|
+
}
|
|
472
|
+
}));
|
|
473
|
+
},
|
|
474
|
+
async dispose() {
|
|
475
|
+
disposed = true;
|
|
476
|
+
await Promise.allSettled(states.map(async (state) => {
|
|
477
|
+
if (state.timer !== undefined) {
|
|
478
|
+
clearTimeout(state.timer);
|
|
479
|
+
state.timer = undefined;
|
|
480
|
+
}
|
|
481
|
+
// A connect still mid-handshake first: closing its transport kills
|
|
482
|
+
// the child and rejects the in-flight awaits, so shutdown does not
|
|
483
|
+
// wait out a connect timeout on a server that is never answering.
|
|
484
|
+
const pending = state.pending;
|
|
485
|
+
state.pending = undefined;
|
|
486
|
+
await pending?.close().catch(() => { });
|
|
487
|
+
const client = state.client;
|
|
488
|
+
state.client = undefined;
|
|
489
|
+
state.connected = false;
|
|
490
|
+
await client?.close();
|
|
491
|
+
}));
|
|
492
|
+
},
|
|
493
|
+
};
|
|
494
|
+
};
|
|
495
|
+
/** The loader's ABI. See `docs/architecture/plugins.md`. */
|
|
496
|
+
export const createPlugin = (config) => createMcpPlugin(config);
|
|
497
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { JsonValue } from '@stratusagent/core';
|
|
2
|
+
/**
|
|
3
|
+
* Turning what an MCP server sends back into what a Stratus tool returns:
|
|
4
|
+
* `JsonValue` out, files on disk where a content block is binary. Split
|
|
5
|
+
* from the client lifecycle because this half is pure enough to test
|
|
6
|
+
* without a connection.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* One segment of a bridged tool's registered name — lowercased, everything
|
|
10
|
+
* that is not `[a-z0-9_-]` folded to `_`, leading punctuation stripped.
|
|
11
|
+
* `undefined` when nothing survives, which the caller must treat as a
|
|
12
|
+
* refusal rather than inventing a name.
|
|
13
|
+
*
|
|
14
|
+
* Folding is what makes a collision possible (`createIssue` and
|
|
15
|
+
* `create_issue` both become `create_issue`), which is why the caller
|
|
16
|
+
* checks for one and refuses it rather than letting the second tool
|
|
17
|
+
* silently answer calls meant for the first.
|
|
18
|
+
*/
|
|
19
|
+
export declare const sanitizeToolSegment: (raw: string) => string | undefined;
|
|
20
|
+
/** Server keys are operator-chosen and become a name segment; held to the segment shape outright. */
|
|
21
|
+
export declare const SERVER_NAME_PATTERN: RegExp;
|
|
22
|
+
/** The registered name a server's tool bridges to: `mcp.<server>.<segment>`. */
|
|
23
|
+
export declare const bridgedToolName: (server: string, segment: string) => string;
|
|
24
|
+
export interface NormalizeOptions {
|
|
25
|
+
/** The bridged server's config key — part of where a binary block lands. */
|
|
26
|
+
server: string;
|
|
27
|
+
/** The server-side tool name — part of the written file's name. */
|
|
28
|
+
tool: string;
|
|
29
|
+
/**
|
|
30
|
+
* The workspace root the host supplied. Binary content lands under
|
|
31
|
+
* `<workspaceRoot>/<agentId>/mcp/<server>/` — per agent, same as
|
|
32
|
+
* screenshots, so two agents never read each other's files.
|
|
33
|
+
*/
|
|
34
|
+
workspaceRoot?: string;
|
|
35
|
+
agentId: string;
|
|
36
|
+
/** Clock seam for deterministic file names in tests. */
|
|
37
|
+
now?: () => number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Normalize one MCP `tools/call` result into a `JsonValue`.
|
|
41
|
+
*
|
|
42
|
+
* - Text blocks join into one `text` string.
|
|
43
|
+
* - Image and audio blocks (and embedded blob resources) are decoded into
|
|
44
|
+
* the agent's workspace and returned under `files` — the key a channel
|
|
45
|
+
* treats as "deliver this as an attachment", which is how an image from a
|
|
46
|
+
* bridged tool reaches Slack.
|
|
47
|
+
* - Embedded text resources join the text, labeled by uri; resource links
|
|
48
|
+
* pass through under `resources` — a link is an offer, not content.
|
|
49
|
+
* - `structuredContent` passes through as `structured`.
|
|
50
|
+
* - A result the server marked `isError` becomes a thrown error, so it
|
|
51
|
+
* lands in `ToolResult.error` like any other failing tool.
|
|
52
|
+
*
|
|
53
|
+
* A result that is only text returns the string itself — the shape the
|
|
54
|
+
* model reads best — and anything richer returns an object.
|
|
55
|
+
*/
|
|
56
|
+
export declare const normalizeCallResult: (result: unknown, options: NormalizeOptions) => Promise<JsonValue>;
|
|
57
|
+
//# sourceMappingURL=normalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEhE;;;;;GAKG;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SAI1D,CAAC;AAEF,qGAAqG;AACrG,eAAO,MAAM,mBAAmB,QAA0B,CAAC;AAE3D,gFAAgF;AAChF,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,EAAE,SAAS,MAAM,KAAG,MAAoC,CAAC;AAkCvG,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,OAAO,EACf,SAAS,gBAAgB,KACxB,OAAO,CAAC,SAAS,CA8FnB,CAAC"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Turning what an MCP server sends back into what a Stratus tool returns:
|
|
5
|
+
* `JsonValue` out, files on disk where a content block is binary. Split
|
|
6
|
+
* from the client lifecycle because this half is pure enough to test
|
|
7
|
+
* without a connection.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* One segment of a bridged tool's registered name — lowercased, everything
|
|
11
|
+
* that is not `[a-z0-9_-]` folded to `_`, leading punctuation stripped.
|
|
12
|
+
* `undefined` when nothing survives, which the caller must treat as a
|
|
13
|
+
* refusal rather than inventing a name.
|
|
14
|
+
*
|
|
15
|
+
* Folding is what makes a collision possible (`createIssue` and
|
|
16
|
+
* `create_issue` both become `create_issue`), which is why the caller
|
|
17
|
+
* checks for one and refuses it rather than letting the second tool
|
|
18
|
+
* silently answer calls meant for the first.
|
|
19
|
+
*/
|
|
20
|
+
export const sanitizeToolSegment = (raw) => {
|
|
21
|
+
const folded = raw.toLowerCase().replace(/[^a-z0-9_-]+/g, '_');
|
|
22
|
+
const trimmed = folded.replace(/^[^a-z0-9]+/, '');
|
|
23
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
24
|
+
};
|
|
25
|
+
/** Server keys are operator-chosen and become a name segment; held to the segment shape outright. */
|
|
26
|
+
export const SERVER_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
|
|
27
|
+
/** The registered name a server's tool bridges to: `mcp.<server>.<segment>`. */
|
|
28
|
+
export const bridgedToolName = (server, segment) => `mcp.${server}.${segment}`;
|
|
29
|
+
const EXTENSION_BY_MIME = {
|
|
30
|
+
'image/png': 'png',
|
|
31
|
+
'image/jpeg': 'jpg',
|
|
32
|
+
'image/gif': 'gif',
|
|
33
|
+
'image/webp': 'webp',
|
|
34
|
+
'image/svg+xml': 'svg',
|
|
35
|
+
'audio/wav': 'wav',
|
|
36
|
+
'audio/mpeg': 'mp3',
|
|
37
|
+
'application/pdf': 'pdf',
|
|
38
|
+
};
|
|
39
|
+
const extensionFor = (mimeType) => {
|
|
40
|
+
if (!mimeType) {
|
|
41
|
+
return 'bin';
|
|
42
|
+
}
|
|
43
|
+
const known = EXTENSION_BY_MIME[mimeType];
|
|
44
|
+
if (known) {
|
|
45
|
+
return known;
|
|
46
|
+
}
|
|
47
|
+
const subtype = mimeType.split('/')[1]?.replace(/[^a-z0-9]/gi, '');
|
|
48
|
+
return subtype && subtype.length > 0 ? subtype.toLowerCase() : 'bin';
|
|
49
|
+
};
|
|
50
|
+
const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
51
|
+
// Distinguishes every written file within one process. A timestamp alone
|
|
52
|
+
// is not enough: two calls in the same millisecond (parallel tool calls in
|
|
53
|
+
// one turn) would produce the same path, and the first result's `files`
|
|
54
|
+
// entry would silently point at the second call's bytes.
|
|
55
|
+
let fileSerial = 0;
|
|
56
|
+
/**
|
|
57
|
+
* Normalize one MCP `tools/call` result into a `JsonValue`.
|
|
58
|
+
*
|
|
59
|
+
* - Text blocks join into one `text` string.
|
|
60
|
+
* - Image and audio blocks (and embedded blob resources) are decoded into
|
|
61
|
+
* the agent's workspace and returned under `files` — the key a channel
|
|
62
|
+
* treats as "deliver this as an attachment", which is how an image from a
|
|
63
|
+
* bridged tool reaches Slack.
|
|
64
|
+
* - Embedded text resources join the text, labeled by uri; resource links
|
|
65
|
+
* pass through under `resources` — a link is an offer, not content.
|
|
66
|
+
* - `structuredContent` passes through as `structured`.
|
|
67
|
+
* - A result the server marked `isError` becomes a thrown error, so it
|
|
68
|
+
* lands in `ToolResult.error` like any other failing tool.
|
|
69
|
+
*
|
|
70
|
+
* A result that is only text returns the string itself — the shape the
|
|
71
|
+
* model reads best — and anything richer returns an object.
|
|
72
|
+
*/
|
|
73
|
+
export const normalizeCallResult = async (result, options) => {
|
|
74
|
+
const shaped = isObject(result) ? result : {};
|
|
75
|
+
const content = Array.isArray(shaped.content) ? shaped.content : [];
|
|
76
|
+
const texts = [];
|
|
77
|
+
const files = [];
|
|
78
|
+
const resources = [];
|
|
79
|
+
// Settled before anything touches the disk: a failing result's binary
|
|
80
|
+
// blocks would otherwise land as server-controlled files in the
|
|
81
|
+
// workspace that nothing references, delivers, or cleans up.
|
|
82
|
+
if (shaped.isError === true) {
|
|
83
|
+
const message = content
|
|
84
|
+
.filter(isObject)
|
|
85
|
+
.filter((block) => block.type === 'text' && typeof block.text === 'string')
|
|
86
|
+
.map((block) => block.text)
|
|
87
|
+
.join('\n\n');
|
|
88
|
+
throw new Error(message || `MCP server ${options.server} reported an error for ${options.tool} with no message.`);
|
|
89
|
+
}
|
|
90
|
+
const writeBlock = async (data, mimeType) => {
|
|
91
|
+
if (typeof data !== 'string') {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!options.workspaceRoot) {
|
|
95
|
+
texts.push(`[binary ${typeof mimeType === 'string' ? mimeType : 'content'} dropped: no workspaceRoot is configured for @stratusagent/plugin-mcp]`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const directory = path.join(options.workspaceRoot, options.agentId, 'mcp', options.server);
|
|
99
|
+
await mkdir(directory, { recursive: true });
|
|
100
|
+
const stamp = (options.now ?? Date.now)();
|
|
101
|
+
fileSerial += 1;
|
|
102
|
+
// The tool name is the server's own string, so it is folded to the
|
|
103
|
+
// name-segment shape before it becomes part of a path: interpolated
|
|
104
|
+
// raw, a tool named `../../…` would be an arbitrary-directory write
|
|
105
|
+
// steered by whoever runs the server.
|
|
106
|
+
const file = path.join(directory, `${sanitizeToolSegment(options.tool) ?? 'tool'}-${stamp}-${fileSerial}.${extensionFor(typeof mimeType === 'string' ? mimeType : undefined)}`);
|
|
107
|
+
await writeFile(file, Buffer.from(data, 'base64'));
|
|
108
|
+
files.push(file);
|
|
109
|
+
};
|
|
110
|
+
for (const block of content) {
|
|
111
|
+
if (!isObject(block)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
switch (block.type) {
|
|
115
|
+
case 'text':
|
|
116
|
+
if (typeof block.text === 'string') {
|
|
117
|
+
texts.push(block.text);
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
case 'image':
|
|
121
|
+
case 'audio':
|
|
122
|
+
await writeBlock(block.data, block.mimeType);
|
|
123
|
+
break;
|
|
124
|
+
case 'resource': {
|
|
125
|
+
const resource = isObject(block.resource) ? block.resource : {};
|
|
126
|
+
if (typeof resource.text === 'string') {
|
|
127
|
+
const uri = typeof resource.uri === 'string' ? resource.uri : undefined;
|
|
128
|
+
texts.push(uri ? `${uri}:\n${resource.text}` : resource.text);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
await writeBlock(resource.blob, resource.mimeType);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case 'resource_link': {
|
|
136
|
+
const link = {};
|
|
137
|
+
for (const key of ['uri', 'name', 'title', 'description', 'mimeType']) {
|
|
138
|
+
if (typeof block[key] === 'string') {
|
|
139
|
+
link[key] = block[key];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
resources.push(link);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
default:
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const structured = isObject(shaped.structuredContent) ? shaped.structuredContent : undefined;
|
|
150
|
+
const text = texts.length > 0 ? texts.join('\n\n') : undefined;
|
|
151
|
+
if (structured === undefined && files.length === 0 && resources.length === 0) {
|
|
152
|
+
return text ?? '';
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
...(text !== undefined ? { text } : {}),
|
|
156
|
+
...(structured !== undefined ? { structured } : {}),
|
|
157
|
+
...(files.length > 0 ? { files } : {}),
|
|
158
|
+
...(resources.length > 0 ? { resources } : {}),
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
//# sourceMappingURL=normalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stratusagent/plugin-mcp",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.13 <23 || >=23.4"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
22
|
+
"@stratusagent/core": "0.9.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^24.6.0",
|
|
26
|
+
"typescript": "^5.9.3",
|
|
27
|
+
"zod": "^4.4.3",
|
|
28
|
+
"@stratusagent/plugins": "0.9.0"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/stratuslabs/agent.git",
|
|
34
|
+
"directory": "packages/plugin-mcp"
|
|
35
|
+
},
|
|
36
|
+
"description": "The MCP bridge for Stratus Agent: mounts any Model Context Protocol server's tools as mcp.<server>.<tool>, under the same allowlists, risk model, and approval policy as native tools",
|
|
37
|
+
"keywords": [
|
|
38
|
+
"stratus",
|
|
39
|
+
"agent",
|
|
40
|
+
"ai",
|
|
41
|
+
"llm",
|
|
42
|
+
"stratus-plugin",
|
|
43
|
+
"mcp",
|
|
44
|
+
"model-context-protocol",
|
|
45
|
+
"tools"
|
|
46
|
+
],
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"stratus": {
|
|
51
|
+
"pluginVersion": 1,
|
|
52
|
+
"contributes": {
|
|
53
|
+
"toolsDiscovered": [
|
|
54
|
+
{
|
|
55
|
+
"namespace": "mcp.*",
|
|
56
|
+
"risk": "gated"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"config": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"properties": {
|
|
63
|
+
"servers": {
|
|
64
|
+
"type": "object"
|
|
65
|
+
},
|
|
66
|
+
"workspaceRoot": {
|
|
67
|
+
"type": "string"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"required": [
|
|
71
|
+
"servers"
|
|
72
|
+
],
|
|
73
|
+
"additionalProperties": false
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"scripts": {
|
|
77
|
+
"build": "tsc -b",
|
|
78
|
+
"typecheck": "tsc --noEmit --project tsconfig.test.json",
|
|
79
|
+
"test": "node --test --experimental-strip-types"
|
|
80
|
+
}
|
|
81
|
+
}
|