@plurnk/plurnk-execs-mcp 0.1.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 PossumTech Laboratories
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,82 @@
1
+ # @plurnk/plurnk-execs-mcp
2
+
3
+ MCP-bridge runtime executor for [plurnk-service](https://github.com/plurnk/plurnk-service)'s `exec` scheme. Each [Model Context Protocol](https://modelcontextprotocol.io/) server you configure becomes its own `EXEC` tag; the server's tools are called from the op body, and the output is contained behind the server's address — READ back slice-wise, never dumped into context.
4
+
5
+ Built on the [plurnk-execs](https://github.com/plurnk/plurnk-execs) framework, using the official [`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk).
6
+
7
+ > **Opt-in.** Unlike the other siblings, this one is **not** in the `@plurnk/plurnk-execs-all` bundle: it pulls the MCP SDK's dependency tree and is inert until you configure a server, so install it explicitly when you want MCP.
8
+
9
+ ## Why a bridge, not a special case
10
+
11
+ An MCP server is an external **tool** surface — which is exactly `exec://` territory. So MCP is not a new client/provider concern; it's just an executor. The model never speaks MCP: it emits `EXEC`, the executor translates to/from the protocol, and the JSON result lands on a `results` channel like any other tool (sqlite, jq, search). The client never knows MCP exists.
12
+
13
+ The payoff is **containment**: a 30-tool server costs **one** hot-path line (the `example`) plus an on-demand tool catalog — instead of dumping every tool's schema into context every turn. That is the thing that makes MCP not suck (plurnk-execs#10).
14
+
15
+ ## Per-deployment tags (dynamic discovery)
16
+
17
+ Tags here aren't known at publish time — they're the servers *you* configure. The package declares no static `plurnk.runtimes[]`; instead it ships a `runtimesModule` hook the framework's `discover()` calls at boot to materialize one tag per configured server (plurnk-execs#10, SPEC §3.1).
18
+
19
+ ## Configuration (environment)
20
+
21
+ Mirrors the model-alias convention (`PLURNK_MODEL_<alias>=<provider>/<model>`): **one var per server, the server is the var**, and the set is discovered by enumerating the namespace — there is no list var. The suffix case-folds to the tag, so `PLURNK_MCP_github` and `PLURNK_MCP_GITHUB` are the same server.
22
+
23
+ | Var | Notes |
24
+ |---|---|
25
+ | `PLURNK_MCP_<server>` | **the server** — its value is the target: an `https://…` URL (streamable-HTTP) **or** a command line (stdio) |
26
+ | `PLURNK_MCP_<server>_ENV` | stdio: JSON env overlay for the child process (where tokens go) |
27
+ | `PLURNK_MCP_<server>_HEADERS` | http: JSON request headers (auth) |
28
+
29
+ The transport is inferred from the target: an `http(s)://` value connects over HTTP, anything else is spawned as a stdio command. `_ENV` / `_HEADERS` are reserved companion suffixes and `INSTALL` is a reserved control key (below), so a server can't be named to end in them or be named `install`. Two keys that case-fold to one server is a fail-hard config error.
30
+
31
+ ### Security: the install gate
32
+
33
+ | Var | Default | Notes |
34
+ |---|---|---|
35
+ | `PLURNK_MCP_INSTALL` | off | may **arbitrary** MCP tooling be **added at runtime** (a future `/mcp` hotload route)? Off ⇒ only env-declared servers exist |
36
+
37
+ The single security boundary is what may be *added*, not what may be *activated*. Servers you declare in env are always honored; enabling or disabling an already-present tag is never gated. Runtime hotloading of operator-unvetted servers is refused unless `PLURNK_MCP_INSTALL=1`.
38
+
39
+ ```bash
40
+ # http server
41
+ PLURNK_MCP_github="https://api.githubcopilot.com/mcp/"
42
+ PLURNK_MCP_github_HEADERS='{"Authorization":"Bearer …"}'
43
+
44
+ # stdio server
45
+ PLURNK_MCP_FIGMA="npx -y figma-developer-mcp --stdio"
46
+ PLURNK_MCP_FIGMA_ENV='{"FIGMA_API_KEY":"…"}'
47
+ ```
48
+
49
+ ## Calling tools
50
+
51
+ ```
52
+ EXEC[<server>]:<tool_name> <json-arguments>:EXEC
53
+ ```
54
+
55
+ `<json-arguments>` is a single JSON object; omit it for a no-argument tool.
56
+
57
+ ```
58
+ EXEC[github]:create_issue {"title":"Bug","body":"…"}:EXEC
59
+ EXEC[github]:list_repos:EXEC
60
+ ```
61
+
62
+ Run a tag with an **empty body** (or `?` / `help`) to write the server's live tool catalog — names, descriptions, input JSON schemas — to the `results` stream:
63
+
64
+ ```
65
+ EXEC[github]::EXEC
66
+ ```
67
+
68
+ ## Output & gating
69
+
70
+ The tool result is written as JSON (`application/json`) to the `results` channel. A tool that reports `isError` closes the channel errored with status 500.
71
+
72
+ Every call is **proposal-gated** (`effect` → `host`): the side-effect class of an individual MCP tool isn't visible to the synchronous, target-only gating hook, so the conservative default applies. Finer `readOnlyHint`-driven auto-run awaits a command-aware gating hook in the consumer.
73
+
74
+ Failures emit a `TelemetryEvent` (`source: "exec:<server>"`): `mcp_not_configured`, `mcp_unreachable`, `mcp_list_failed`, `mcp_bad_arguments`, `mcp_tool_error`.
75
+
76
+ ## Lifecycle
77
+
78
+ Connections are long-lived and cached (one client per server, opened lazily, reused across runs). `closeAll()` disconnects every open server — call it on daemon shutdown so child stdio servers don't leak.
79
+
80
+ ## Tests
81
+
82
+ `test:lint`, `test:unit` — the unit suite spawns a real stdio MCP server fixture and exercises the bridge end-to-end (probe, tool call, catalog, error, abort).
package/dist/Mcp.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { BaseExecutor } from "@plurnk/plurnk-execs";
2
+ import type { ChannelDecl, Effect, ExecArgs, ExecResult, RuntimeAvailability } from "@plurnk/plurnk-execs";
3
+ export declare function closeAll(): Promise<void>;
4
+ export default class Mcp extends BaseExecutor {
5
+ get channels(): Readonly<Record<string, ChannelDecl>>;
6
+ probe(): Promise<RuntimeAvailability>;
7
+ effect(_target: string | null): Effect;
8
+ run({ runtime, command, signal, write, setState, emit }: ExecArgs): Promise<ExecResult>;
9
+ }
10
+ //# sourceMappingURL=Mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Mcp.d.ts","sourceRoot":"","sources":["../src/Mcp.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAiC3G,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAI9C;AAUD,MAAM,CAAC,OAAO,OAAO,GAAI,SAAQ,YAAY;IACzC,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAEpD;IAKc,KAAK,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAkB3C,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;IAIzC,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;CA0DhG"}
package/dist/Mcp.js ADDED
@@ -0,0 +1,142 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport, getDefaultEnvironment } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4
+ import { BaseExecutor } from "@plurnk/plurnk-execs";
5
+ import { serverConfig } from "./config.js";
6
+ const CLIENT_VERSION = "0.1.0";
7
+ // MCP connections are long-lived: open one Client per server, lazily, and reuse
8
+ // it across runs (the wasm `wabtPromise` singleton precedent). Keyed by tag
9
+ // name. A failed connection is evicted so the next call reconnects from scratch.
10
+ const clients = new Map();
11
+ const connect = (name, cfg) => {
12
+ const existing = clients.get(name);
13
+ if (existing)
14
+ return existing;
15
+ const pending = open(cfg).catch((err) => {
16
+ if (clients.get(name) === pending)
17
+ clients.delete(name);
18
+ throw err;
19
+ });
20
+ clients.set(name, pending);
21
+ return pending;
22
+ };
23
+ const open = async (cfg) => {
24
+ const transport = cfg.transport === "stdio"
25
+ ? new StdioClientTransport({ command: cfg.command, args: cfg.args, env: { ...getDefaultEnvironment(), ...cfg.env } })
26
+ : new StreamableHTTPClientTransport(new URL(cfg.url), cfg.headers ? { requestInit: { headers: cfg.headers } } : undefined);
27
+ const client = new Client({ name: "plurnk-execs-mcp", version: CLIENT_VERSION });
28
+ await client.connect(transport);
29
+ return client;
30
+ };
31
+ // Disconnect every open MCP server and drop the cache. The consumer calls this
32
+ // on daemon shutdown so child stdio servers don't leak; idempotent and never
33
+ // throws (a close failure on one server doesn't block the rest).
34
+ export async function closeAll() {
35
+ const open = [...clients.values()];
36
+ clients.clear();
37
+ await Promise.allSettled(open.map(async (p) => { (await p).close(); }));
38
+ }
39
+ const msg = (err) => (err instanceof Error ? err.message : String(err));
40
+ // MCP-bridge executor. Each configured server is one tag (this.runtime); the
41
+ // EXEC body is `<tool_name> <json-args>` (or empty / `?` / `help` for the live
42
+ // tool catalog). Tool output is written as JSON to the `results` channel —
43
+ // contained behind the server's address, READ back rather than dumped inline.
44
+ // Stateless beyond the module-level connection cache; configuration is read from
45
+ // the environment per run (see config.ts).
46
+ export default class Mcp extends BaseExecutor {
47
+ get channels() {
48
+ return { results: { mimetype: "application/json" } };
49
+ }
50
+ // Available iff the server is configured AND reachable, reporting its tool
51
+ // count — boot answers "is this MCP server up?". A connection failure is a
52
+ // settled unavailable, not a throw, so one dead server doesn't fail boot.
53
+ async probe() {
54
+ const cfg = serverConfig(this.runtime);
55
+ if (cfg === null) {
56
+ return { available: false, detail: `MCP server '${this.runtime}' not configured (set PLURNK_MCP_${this.runtime.toUpperCase()}=<url-or-command>)` };
57
+ }
58
+ try {
59
+ const { tools } = await connect(this.runtime, cfg).then((c) => c.listTools());
60
+ return { available: true, detail: `${cfg.transport}: ${tools.length} tool${tools.length === 1 ? "" : "s"}` };
61
+ }
62
+ catch (err) {
63
+ return { available: false, detail: `MCP '${this.runtime}' unreachable: ${msg(err)}` };
64
+ }
65
+ }
66
+ // Conservative `host` for every call: the side-effect class depends on which
67
+ // tool the body names and the server's per-tool annotations, neither visible
68
+ // to this target-only, synchronous hook — so MCP calls are always
69
+ // proposal-gated. Finer auto-vs-propose by `readOnlyHint` awaits a
70
+ // command-aware gating hook in the consumer.
71
+ effect(_target) {
72
+ return "host";
73
+ }
74
+ async run({ runtime, command, signal, write, setState, emit }) {
75
+ const cfg = serverConfig(runtime);
76
+ const fail = (kind, message, status = 500) => {
77
+ emit({ source: `exec:${runtime}`, kind, message });
78
+ setState("results", "errored");
79
+ return { status };
80
+ };
81
+ if (cfg === null)
82
+ return fail("mcp_not_configured", `MCP server '${runtime}' is not configured`);
83
+ let client;
84
+ try {
85
+ client = await connect(runtime, cfg);
86
+ }
87
+ catch (err) {
88
+ if (signal.aborted) {
89
+ setState("results", "errored");
90
+ return { status: 499 };
91
+ }
92
+ return fail("mcp_unreachable", `MCP '${runtime}' connect failed: ${msg(err)}`);
93
+ }
94
+ const body = command.trim();
95
+ // Empty / `?` / `help` body → the live tool catalog.
96
+ if (body === "" || body === "?" || body === "help") {
97
+ try {
98
+ const { tools } = await client.listTools(undefined, { signal });
99
+ write("results", JSON.stringify(tools), "application/json");
100
+ setState("results", "closed");
101
+ return { status: 200 };
102
+ }
103
+ catch (err) {
104
+ if (signal.aborted) {
105
+ setState("results", "errored");
106
+ return { status: 499 };
107
+ }
108
+ return fail("mcp_list_failed", `listing tools for '${runtime}' failed: ${msg(err)}`);
109
+ }
110
+ }
111
+ // `<tool_name> <json-args>` — split on the first whitespace run.
112
+ const sep = body.search(/\s/);
113
+ const tool = sep === -1 ? body : body.slice(0, sep);
114
+ const argsRaw = sep === -1 ? "" : body.slice(sep + 1).trim();
115
+ let toolArgs = {};
116
+ if (argsRaw !== "") {
117
+ try {
118
+ toolArgs = JSON.parse(argsRaw);
119
+ }
120
+ catch (err) {
121
+ return fail("mcp_bad_arguments", `tool arguments for '${tool}' must be a JSON object: ${msg(err)}`, 400);
122
+ }
123
+ }
124
+ try {
125
+ const result = await client.callTool({ name: tool, arguments: toolArgs }, undefined, { signal });
126
+ write("results", JSON.stringify(result), "application/json");
127
+ // An MCP tool reports its own failure via `isError` rather than
128
+ // throwing; honor it as an errored close with a 500.
129
+ const errored = result.isError === true;
130
+ setState("results", errored ? "errored" : "closed");
131
+ return { status: errored ? 500 : 200 };
132
+ }
133
+ catch (err) {
134
+ if (signal.aborted) {
135
+ setState("results", "errored");
136
+ return { status: 499 };
137
+ }
138
+ return fail("mcp_tool_error", `tool '${tool}' on '${runtime}' failed: ${msg(err)}`);
139
+ }
140
+ }
141
+ }
142
+ //# sourceMappingURL=Mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Mcp.js","sourceRoot":"","sources":["../src/Mcp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface ServerConfig {
2
+ transport: "stdio" | "http";
3
+ command?: string;
4
+ args?: string[];
5
+ env?: Record<string, string>;
6
+ url?: string;
7
+ headers?: Record<string, string>;
8
+ }
9
+ export declare const installAllowed: (env?: NodeJS.ProcessEnv) => boolean;
10
+ export declare const serverNames: (env?: NodeJS.ProcessEnv) => string[];
11
+ export declare const serverConfig: (name: string, env?: NodeJS.ProcessEnv) => ServerConfig | null;
12
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AA+BA,MAAM,WAAW,YAAY;IACzB,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAmDD,eAAO,MAAM,cAAc,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,OAGrE,CAAC;AAIF,eAAO,MAAM,WAAW,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,MAAM,EAChC,CAAC;AAI1C,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,EAAE,MAAK,MAAM,CAAC,UAAwB,KAAG,YAAY,GAAG,IAUhG,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,102 @@
1
+ // Per-server configuration, mirroring the model-alias convention
2
+ // (PLURNK_MODEL_<alias>=<provider>/<model>, plurnk-providers): one env var per
3
+ // server, the server IS the var, and the set is discovered by ENUMERATING the
4
+ // namespace — no separate list var. The suffix case-folds to the tag.
5
+ //
6
+ // PLURNK_MCP_<server> = <target> the server. Target is an http(s)
7
+ // URL (streamable-HTTP transport) or
8
+ // a command line (stdio transport).
9
+ // PLURNK_MCP_<server>_ENV = <json> stdio: env overlay for the child (tokens)
10
+ // PLURNK_MCP_<server>_HEADERS = <json> http: request headers (auth)
11
+ // PLURNK_MCP_INSTALL = 0|1 daemon gate: may arbitrary MCP
12
+ // tooling be ADDED at runtime (the
13
+ // /mcp hotload route)? Default off.
14
+ //
15
+ // Servers case-fold (PLURNK_MCP_github === PLURNK_MCP_GITHUB); two keys folding
16
+ // to the same server is fail-hard, exactly as model aliases are. `_ENV` /
17
+ // `_HEADERS` are reserved companion suffixes and `INSTALL` is a reserved control
18
+ // key, so a server can't be named to end in those or to be named `install`.
19
+ // This module is SDK-free — it only parses the environment, so the runtimes
20
+ // hook that imports it pulls no transport code at discovery time.
21
+ const PREFIX = "PLURNK_MCP_";
22
+ const ENV_SUFFIX = "_env";
23
+ const HEADERS_SUFFIX = "_headers";
24
+ // Reserved control keys in the PLURNK_MCP_* namespace — daemon switches, never
25
+ // servers. (A server therefore can't be named for one of these.)
26
+ const CONTROL_KEYS = new Set(["install"]);
27
+ // One pass over the environment, partitioning `PLURNK_MCP_*` into server targets
28
+ // and their `_ENV` / `_HEADERS` companions (all keyed by the case-folded server
29
+ // name). The prefix is fixed upper-case; only the suffix case varies, so the
30
+ // user's `PLURNK_MCP_github` and a conventional `PLURNK_MCP_GITHUB` both resolve.
31
+ const parse = (environ) => {
32
+ const targets = new Map();
33
+ const envs = new Map();
34
+ const headers = new Map();
35
+ for (const [key, value] of Object.entries(environ)) {
36
+ if (value === undefined || !key.startsWith(PREFIX))
37
+ continue;
38
+ const suffix = key.slice(PREFIX.length);
39
+ if (suffix.length === 0)
40
+ continue;
41
+ const name = suffix.toLowerCase();
42
+ if (CONTROL_KEYS.has(name))
43
+ continue; // a daemon control switch, not a server
44
+ if (name.endsWith(ENV_SUFFIX)) {
45
+ envs.set(name.slice(0, -ENV_SUFFIX.length), value);
46
+ continue;
47
+ }
48
+ if (name.endsWith(HEADERS_SUFFIX)) {
49
+ headers.set(name.slice(0, -HEADERS_SUFFIX.length), value);
50
+ continue;
51
+ }
52
+ if (targets.has(name))
53
+ throw new Error(`Duplicate MCP server "${name}": multiple ${PREFIX}* keys case-fold to the same server. Rename one.`);
54
+ targets.set(name, value);
55
+ }
56
+ return { targets, envs, headers };
57
+ };
58
+ // Parse an env value as a JSON object, fail-hard on malformed config — a
59
+ // declared but unparseable companion is an operator mistake, surfaced not
60
+ // dropped. Absent → undefined.
61
+ const jsonRecord = (raw, what) => {
62
+ if (!raw)
63
+ return undefined;
64
+ let parsed;
65
+ try {
66
+ parsed = JSON.parse(raw);
67
+ }
68
+ catch (cause) {
69
+ throw new Error(`${what} must be a JSON object`, { cause });
70
+ }
71
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
72
+ throw new Error(`${what} must be a JSON object`);
73
+ return parsed;
74
+ };
75
+ // Whether the daemon permits ADDING arbitrary MCP tooling at runtime (the /mcp
76
+ // hotload route) — the single security boundary for runtime registration.
77
+ // Env-declared servers are always honored; only runtime-injected,
78
+ // operator-unvetted servers are gated by this. Enabling/disabling an
79
+ // already-present tag is NOT gated. Default OFF (absent / "" / "0"); opt in with
80
+ // PLURNK_MCP_INSTALL=1. Mirrors the trust-gate truthiness convention.
81
+ export const installAllowed = (env = process.env) => {
82
+ const gate = env.PLURNK_MCP_INSTALL;
83
+ return !(gate === undefined || gate === "" || gate === "0");
84
+ };
85
+ // The configured server names (= the tags), case-folded and sorted. Empty when
86
+ // none are set.
87
+ export const serverNames = (env = process.env) => [...parse(env).targets.keys()].sort();
88
+ // Resolve a server's transport config. An http(s) URL target → http; anything
89
+ // else → a stdio command line. null when the server names no target.
90
+ export const serverConfig = (name, env = process.env) => {
91
+ const { targets, envs, headers } = parse(env);
92
+ const key = name.toLowerCase();
93
+ const target = targets.get(key);
94
+ if (target === undefined)
95
+ return null;
96
+ if (/^https?:\/\//i.test(target)) {
97
+ return { transport: "http", url: target, headers: jsonRecord(headers.get(key), `${PREFIX}${key.toUpperCase()}_HEADERS`) };
98
+ }
99
+ const [command, ...args] = target.split(/\s+/);
100
+ return { transport: "stdio", command, args, env: jsonRecord(envs.get(key), `${PREFIX}${key.toUpperCase()}_ENV`) };
101
+ };
102
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { default as Mcp } from "./Mcp.ts";
2
+ export { default } from "./Mcp.ts";
3
+ export { runtimes } from "./runtimes.ts";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { default as Mcp } from "./Mcp.js";
2
+ export { default } from "./Mcp.js";
3
+ export { runtimes } from "./runtimes.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { RuntimeDecl } from "@plurnk/plurnk-execs";
2
+ export declare function runtimes(): RuntimeDecl[];
3
+ //# sourceMappingURL=runtimes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtimes.d.ts","sourceRoot":"","sources":["../src/runtimes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAexD,wBAAgB,QAAQ,IAAI,WAAW,EAAE,CAOxC"}
@@ -0,0 +1,58 @@
1
+ import { serverNames } from "./config.js";
2
+ const GLYPH = "🔌";
3
+ // The dynamic runtimes hook (plurnk-execs#10), pointed at by this package's
4
+ // `plurnk.runtimesModule`. The framework's `discover()` imports and calls it at
5
+ // scan time — but only for a trusted package — to materialize the per-deployment
6
+ // tags a static `plurnk.runtimes[]` can't express.
7
+ //
8
+ // Each MCP server an operator declares as `PLURNK_MCP_<server>=<target>` becomes
9
+ // one EXEC tag named for the server (the model-alias convention); its tools are
10
+ // called from the EXEC body. Reads the environment ONLY — no network — so
11
+ // discovery stays cheap and offline; reachability and the live tool catalog are
12
+ // probe()/run()'s job at boot/call.
13
+ export function runtimes() {
14
+ return serverNames().map((name) => ({
15
+ name,
16
+ glyph: GLYPH,
17
+ example: `EXEC[${name}]:tool_name {"arg":"value"}:EXEC`,
18
+ documentation: doc(name),
19
+ }));
20
+ }
21
+ // Per-tag documentation served on demand. Static usage — the *live* tool
22
+ // catalog is fetched by running the tag with an empty (or `?`) body, since
23
+ // listing tools requires a connection we deliberately don't open at scan time.
24
+ const doc = (name) => `# ${name} (MCP)
25
+
26
+ An MCP server bridged as an EXEC tag. Its output lands behind \`${name}://\` and
27
+ is READ back slice-wise — never dumped inline.
28
+
29
+ ## Calling a tool
30
+
31
+ \`\`\`
32
+ EXEC[${name}]:<tool_name> <json-arguments>:EXEC
33
+ \`\`\`
34
+
35
+ \`<json-arguments>\` is a single JSON object (omit it for a no-argument tool):
36
+
37
+ \`\`\`
38
+ EXEC[${name}]:create_issue {"title":"Bug","body":"…"}:EXEC
39
+ EXEC[${name}]:list_repos:EXEC
40
+ \`\`\`
41
+
42
+ ## Discovering tools
43
+
44
+ Run the tag with an empty body (or \`?\` / \`help\`) to write the server's live
45
+ tool catalog — names, descriptions, and input JSON schemas — to the results
46
+ stream:
47
+
48
+ \`\`\`
49
+ EXEC[${name}]::EXEC
50
+ \`\`\`
51
+
52
+ ## Output & gating
53
+
54
+ The tool result is JSON on the \`results\` channel. Every call is proposal-gated
55
+ (treated as host-effecting) — the side-effect class of an individual MCP tool
56
+ isn't known at gate time, so the conservative default applies.
57
+ `;
58
+ //# sourceMappingURL=runtimes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtimes.js","sourceRoot":"","sources":["../src/runtimes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,KAAK,GAAG,IAAI,CAAC;AAEnB,4EAA4E;AAC5E,gFAAgF;AAChF,iFAAiF;AACjF,mDAAmD;AACnD,EAAE;AACF,iFAAiF;AACjF,gFAAgF;AAChF,0EAA0E;AAC1E,gFAAgF;AAChF,oCAAoC;AACpC,MAAM,UAAU,QAAQ;IACpB,OAAO,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAChC,IAAI;QACJ,KAAK,EAAE,KAAK;QACZ,OAAO,EAAE,QAAQ,IAAI,kCAAkC;QACvD,aAAa,EAAE,GAAG,CAAC,IAAI,CAAC;KAC3B,CAAC,CAAC,CAAC;AACR,CAAC;AAED,yEAAyE;AACzE,2EAA2E;AAC3E,+EAA+E;AAC/E,MAAM,GAAG,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,KAAK,IAAI;;kEAEmB,IAAI;;;;;;OAM/D,IAAI;;;;;;OAMJ,IAAI;OACJ,IAAI;;;;;;;;;;OAUJ,IAAI;;;;;;;;CAQV,CAAC"}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@plurnk/plurnk-execs-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP-bridge runtime executor for plurnk-service's exec scheme — exposes each configured MCP server as its own EXEC tag, its tools called from the body, output contained behind the server's address.",
5
+ "keywords": [
6
+ "plurnk",
7
+ "exec",
8
+ "runtime",
9
+ "executor",
10
+ "mcp",
11
+ "model-context-protocol"
12
+ ],
13
+ "homepage": "https://github.com/plurnk/plurnk-execs-mcp#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/plurnk/plurnk-execs-mcp/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/plurnk/plurnk-execs-mcp.git"
20
+ },
21
+ "engines": {
22
+ "node": ">=25"
23
+ },
24
+ "license": "MIT",
25
+ "author": "@wikitopian",
26
+ "type": "module",
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "plurnk": {
31
+ "kind": "exec",
32
+ "attribution": "mcp",
33
+ "runtimesModule": "./dist/runtimes.js"
34
+ },
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/index.d.ts",
38
+ "default": "./dist/index.js"
39
+ },
40
+ "./package.json": "./package.json"
41
+ },
42
+ "files": [
43
+ "dist/**/*",
44
+ "README.md"
45
+ ],
46
+ "scripts": {
47
+ "test:lint": "tsc --noEmit",
48
+ "test:unit": "node --test \"src/**/*.test.ts\"",
49
+ "test": "npm run test:lint && npm run test:unit",
50
+ "build:dist": "tsc -p tsconfig.build.json",
51
+ "build": "npm run build:dist",
52
+ "prepare": "npm run build"
53
+ },
54
+ "dependencies": {
55
+ "@modelcontextprotocol/sdk": "^1.29.0"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^25.8.0",
59
+ "typescript": "^6.0.3",
60
+ "@plurnk/plurnk-execs": "^0.4.16"
61
+ },
62
+ "peerDependencies": {
63
+ "@plurnk/plurnk-execs": "^0.4.16"
64
+ }
65
+ }