@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 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.
@@ -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
+ }