sealnet-mcp 0.4.0 → 0.5.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/README.md +1 -0
- package/dist/cli.cjs +143 -3
- package/dist/cli.js +143 -3
- package/dist/index.cjs +145 -5
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +143 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ When an MCP-connected AI agent (Claude Desktop, Cursor, Cline, etc.) creates a S
|
|
|
19
19
|
| `seal_request(what, kind='any'\|'secret'\|'file', where_url?, wait=true)` / `seal_request(request)` | Ask the user for a file or a secret without it passing through the chat. On the user's computer the `seal` CLI draws the system dialog (`seal request --local --json`); otherwise the user answers at `seal.net/i/<id>`, sealed to this server's key, shown with three emoji of that key. Returns the path of a 0600 file, never the value |
|
|
20
20
|
| `seal_open(url\|item, mode='metadata'\|'file'\|'inline', dir?)` | Read metadata (default — no download, no counter consumption), save to disk (`file`: streamed, verified before it gets its name, 0600, returns paths) or inline parse (whitelisted MIMEs only, ≤100 KB, refuses secrets, HTML and binary). `item` is an inbox handle instead of a link |
|
|
21
21
|
| `seal_inbox(wait?)` | Seals other agents or people sent to this server's key (its address): handles, the sender's three emoji and name, never a link. `seal_share mode=forward to=<address>` puts a link there; `wait` (0 to 300 s) waits for something new |
|
|
22
|
+
| `seal_run(command, secrets, cwd?, timeout?)` | Run a program with secrets from links or inbox items in its environment, through the `seal` CLI (`seal run --redact`): every value is replaced with `[sealed:NAME]` in the output that comes back, and none enters this server. Needs `seal` on PATH (`run_cli_missing` otherwise). Not a sandbox: a program that prints a value transformed escapes the redaction |
|
|
22
23
|
| `seal_list()` | List SEALs created by **this MCP server only** (scope: `state.json`; never reveals user's CLI seals) |
|
|
23
24
|
| `seal_revoke(handle)` | Revoke by opaque handle (model never learns raw `seal_id`) |
|
|
24
25
|
|
package/dist/cli.cjs
CHANGED
|
@@ -11811,7 +11811,7 @@ var seal_mcp_v1_default = {
|
|
|
11811
11811
|
progress: "When a `tools/call` carries `_meta.progressToken`, `seal_share` and `seal_open mode=file` send `notifications/progress` with `progress` = ciphertext bytes done (upload: confirmed by storage) and `total` = ciphertext bytes in all; at most one per second, `progress` strictly increasing, the last one at `progress = total`."
|
|
11812
11812
|
},
|
|
11813
11813
|
server_instructions: {
|
|
11814
|
-
text: "SEAL moves files and secrets between people and agents without their content entering this conversation. When someone wants a file, folder or secret sent to them, to another machine or to another agent, call seal_share with its path and do not open, print or Read it first. When you need an API key, password, file or folder from the user, or they offer or ask how to give you one, call seal_request right away instead of explaining other ways or asking them to paste or upload it. Open a SEAL link someone gave you with seal_open (mode=file for secrets and large files).
|
|
11814
|
+
text: "SEAL moves files and secrets between people and agents without their content entering this conversation. When someone wants a file, folder or secret sent to them, to another machine or to another agent, call seal_share with its path and do not open, print or Read it first. When you need an API key, password, file or folder from the user, or they offer or ask how to give you one, call seal_request right away instead of explaining other ways or asking them to paste or upload it. Open a SEAL link someone gave you with seal_open (mode=file for secrets and large files). Anything sent to you without a link (to your key, your address) waits in seal_inbox: call it, then seal_open item. To run a program with a secret in its environment, use seal_run: the value stays out of the chat. Never read or print a SEAL handoff file.",
|
|
11815
11815
|
note: "The `instructions` of the `initialize` result (SPEC-AGENTS \xA78.1). Hosts that defer MCP tools behind a tool search (Claude Code) show the model only tool names until it searches, but put these instructions into the system prompt: this is the one text a model reads before it decides to look for a tool, or reads the file it was asked to send."
|
|
11816
11816
|
},
|
|
11817
11817
|
tools: {
|
|
@@ -12011,7 +12011,7 @@ var seal_mcp_v1_default = {
|
|
|
12011
12011
|
},
|
|
12012
12012
|
seal_inbox: {
|
|
12013
12013
|
title: "Check the inbox",
|
|
12014
|
-
description: "
|
|
12014
|
+
description: "What other agents or people sent to you: seals addressed to your key (your address), no link needed. Returns handles; open one with seal_open item=<handle>. `wait`: seconds to wait for something new, 0 to 300.",
|
|
12015
12015
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
12016
12016
|
input: {
|
|
12017
12017
|
properties: {
|
|
@@ -12043,6 +12043,46 @@ var seal_mcp_v1_default = {
|
|
|
12043
12043
|
MUST_NOT_contain: ["path", "fragment", "url", "seal_id", "share_code"]
|
|
12044
12044
|
}
|
|
12045
12045
|
},
|
|
12046
|
+
seal_run: {
|
|
12047
|
+
title: "Run a program with secrets",
|
|
12048
|
+
description: "Run a program with secrets from SEAL links or your inbox in its environment. The values never enter this conversation: they are removed from the output you get back. Use it instead of reading a secret file.",
|
|
12049
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
12050
|
+
input: {
|
|
12051
|
+
required: ["command", "secrets"],
|
|
12052
|
+
properties: {
|
|
12053
|
+
command: { type: "array", items: { type: "string" }, minItems: 1, description: "The program and its arguments, run without a shell." },
|
|
12054
|
+
secrets: {
|
|
12055
|
+
type: "array",
|
|
12056
|
+
minItems: 1,
|
|
12057
|
+
maxItems: 10,
|
|
12058
|
+
description: "Each one is a link or an inbox item carrying one text file (up to 64 KiB), put into one variable of the program's environment.",
|
|
12059
|
+
items: {
|
|
12060
|
+
one_of: ["url", "item"],
|
|
12061
|
+
properties: {
|
|
12062
|
+
url: { type: "string", format: "uri", description: "Full SEAL share URL including fragment." },
|
|
12063
|
+
item: { type: "string", pattern: "^in_", description: "An inbox handle from seal_inbox (`in_\u2026`), instead of a URL." },
|
|
12064
|
+
name: { type: "string", pattern: "^[A-Za-z_][A-Za-z0-9_]*$", description: "The variable; default: the file's name in capitals (`Brave API key` \u2192 `BRAVE_API_KEY`)." }
|
|
12065
|
+
}
|
|
12066
|
+
}
|
|
12067
|
+
},
|
|
12068
|
+
cwd: { type: "string", description: "Working directory of the program; default this server's." },
|
|
12069
|
+
timeout: { type: "integer", minimum: 1, maximum: 600, default: 120, description: "Seconds; after them the program is killed." }
|
|
12070
|
+
}
|
|
12071
|
+
},
|
|
12072
|
+
flow: "SPEC-ADDRESS F4. Runs `seal run --redact --json --seal <url>=<NAME> \u2026 --item <inbox id>=<NAME> \u2026 -- <command\u2026>`, the `seal` CLI found on PATH as seal_request finds it, with this server's key as SEAL_SEED in the CLI's environment only and stdin closed. The CLI opens the links and inbox items with that key, removes SEAL_SEED and SEAL_PASSWORD from the program's environment and replaces every value of 8 bytes or more in its output with `[sealed:NAME]`. The values never enter this server's memory (\u04182). After `timeout` the CLI and the program are killed as one process group. Not a sandbox: the host lets the model run commands anyway, and a program that prints a value transformed (base64, one character at a time) escapes the redaction.",
|
|
12073
|
+
output: {
|
|
12074
|
+
type: "object",
|
|
12075
|
+
required: ["exit_code", "stdout", "stderr", "redactions", "truncated"],
|
|
12076
|
+
properties: {
|
|
12077
|
+
exit_code: { type: "integer", description: "The program's; 128 + the signal when a signal killed it." },
|
|
12078
|
+
stdout: { type: "string", description: "The last 64 KiB of the program's stdout, values replaced." },
|
|
12079
|
+
stderr: { type: "string", description: "The last 64 KiB of its stderr, values replaced." },
|
|
12080
|
+
redactions: { type: "integer", minimum: 0, description: "How many values were replaced." },
|
|
12081
|
+
truncated: { type: "boolean", description: "A stream was longer than 64 KiB: only its end is here." }
|
|
12082
|
+
},
|
|
12083
|
+
MUST_NOT_contain: ["value", "secret_value", "content"]
|
|
12084
|
+
}
|
|
12085
|
+
},
|
|
12046
12086
|
seal_list: {
|
|
12047
12087
|
title: "List SEALs created via THIS MCP server",
|
|
12048
12088
|
description: "Seals you created through this server: opaque handles with status and expiry. Seals made with the CLI or the desktop client live elsewhere and are not listed.",
|
|
@@ -12390,6 +12430,10 @@ var seal_mcp_v1_default = {
|
|
|
12390
12430
|
request_cancelled: "seal_request: the user closed the dialog or declined the link. Ask again only if they want to.",
|
|
12391
12431
|
request_expired: "seal_request: nobody answered the link before it expired. Make a new request if still needed.",
|
|
12392
12432
|
request_failed: "seal_request: the local dialog or seal.net failed for a reason other than the above; the message says which.",
|
|
12433
|
+
run_cli_missing: "seal_run: there is no `seal` CLI on PATH. Install it: curl -fsSL https://seal.net/install.sh | sh",
|
|
12434
|
+
run_not_text: "seal_run: a link carries more than one file, or a file over 64 KiB or not UTF-8 text: not a secret for an environment.",
|
|
12435
|
+
run_timeout: "seal_run: the program ran longer than `timeout` and was killed with the CLI.",
|
|
12436
|
+
run_failed: "seal_run: the CLI refused before the program started (a bad or gone link, a password, two secrets for one variable, a program that does not start); the message has the CLI's code.",
|
|
12393
12437
|
pro_secret_not_found: "seal_pro_secret_use: no secret with that id or path in the organisation, or it has no version yet.",
|
|
12394
12438
|
pro_secret_not_granted: "seal_pro_secret_use: the secret exists but this version is not granted to this agent, or the grant was revoked. A person grants it on the secret's page.",
|
|
12395
12439
|
pro_secret_expired: "seal_pro_secret_use: the secret version has expired.",
|
|
@@ -13474,6 +13518,68 @@ async function sealRevoke(ctx, input) {
|
|
|
13474
13518
|
return { revoked: true, revokedAt: now };
|
|
13475
13519
|
}
|
|
13476
13520
|
|
|
13521
|
+
// src/tools/run.ts
|
|
13522
|
+
var import_node_child_process3 = require("child_process");
|
|
13523
|
+
async function sealRun(ctx, input) {
|
|
13524
|
+
const cli = findSealCli();
|
|
13525
|
+
if (!cli) throw new ToolError("run_cli_missing", "there is no `seal` CLI on PATH. Install it: curl -fsSL https://seal.net/install.sh | sh");
|
|
13526
|
+
const args = ["--json", "run", "--redact"];
|
|
13527
|
+
for (const secret of input.secrets) {
|
|
13528
|
+
if (secret.url === void 0 === (secret.item === void 0)) {
|
|
13529
|
+
throw new ToolError("run_failed", "each secret takes exactly one of `url` (a SEAL link) and `item` (an inbox handle from seal_inbox)");
|
|
13530
|
+
}
|
|
13531
|
+
const name = secret.name === void 0 ? "" : `=${secret.name}`;
|
|
13532
|
+
if (secret.url !== void 0) args.push("--seal", secret.url + name);
|
|
13533
|
+
else args.push("--item", (await inboxItem(ctx, secret.item)).id + name);
|
|
13534
|
+
}
|
|
13535
|
+
args.push("--", ...input.command);
|
|
13536
|
+
const env = { ...process.env, SEAL_SEED: bytesToBase64url(ctx.identity.secret) };
|
|
13537
|
+
const { stdout, timedOut } = await runGroup(cli, args, { cwd: input.cwd, env, timeoutMs: input.timeout * 1e3 });
|
|
13538
|
+
if (timedOut) throw new ToolError("run_timeout", `the program ran longer than ${input.timeout} s and was killed`);
|
|
13539
|
+
let answer;
|
|
13540
|
+
try {
|
|
13541
|
+
answer = JSON.parse(stdout);
|
|
13542
|
+
} catch {
|
|
13543
|
+
throw new ToolError("run_failed", "the `seal` CLI answered no JSON: it is older than 1.4.0 (seal run --redact). Update it: curl -fsSL https://seal.net/install.sh | sh");
|
|
13544
|
+
}
|
|
13545
|
+
if ("error" in answer) {
|
|
13546
|
+
throw new ToolError(answer.error.code === "run.not_text" ? "run_not_text" : "run_failed", `seal run: ${answer.error.code}: ${answer.error.message}`);
|
|
13547
|
+
}
|
|
13548
|
+
return {
|
|
13549
|
+
exitCode: answer.exit_code,
|
|
13550
|
+
stdout: answer.stdout,
|
|
13551
|
+
stderr: answer.stderr,
|
|
13552
|
+
redactions: answer.redactions,
|
|
13553
|
+
truncated: answer.truncated
|
|
13554
|
+
};
|
|
13555
|
+
}
|
|
13556
|
+
function runGroup(cli, args, opts) {
|
|
13557
|
+
const group = process.platform !== "win32";
|
|
13558
|
+
return new Promise((resolve5, reject) => {
|
|
13559
|
+
const child = (0, import_node_child_process3.spawn)(cli, args, { cwd: opts.cwd, env: opts.env, stdio: ["ignore", "pipe", "ignore"], detached: group });
|
|
13560
|
+
let stdout = "";
|
|
13561
|
+
let timedOut = false;
|
|
13562
|
+
child.stdout.setEncoding("utf8");
|
|
13563
|
+
child.stdout.on("data", (chunk) => stdout += chunk);
|
|
13564
|
+
const timer = setTimeout(() => {
|
|
13565
|
+
timedOut = true;
|
|
13566
|
+
try {
|
|
13567
|
+
if (group && child.pid !== void 0) process.kill(-child.pid, "SIGKILL");
|
|
13568
|
+
else child.kill("SIGKILL");
|
|
13569
|
+
} catch {
|
|
13570
|
+
}
|
|
13571
|
+
}, opts.timeoutMs);
|
|
13572
|
+
child.on("error", (err2) => {
|
|
13573
|
+
clearTimeout(timer);
|
|
13574
|
+
reject(new ToolError("run_failed", `could not start ${cli}: ${err2.message}`, err2));
|
|
13575
|
+
});
|
|
13576
|
+
child.on("close", () => {
|
|
13577
|
+
clearTimeout(timer);
|
|
13578
|
+
resolve5({ stdout, timedOut });
|
|
13579
|
+
});
|
|
13580
|
+
});
|
|
13581
|
+
}
|
|
13582
|
+
|
|
13477
13583
|
// src/tools/share.ts
|
|
13478
13584
|
var import_node_fs6 = require("fs");
|
|
13479
13585
|
var import_promises9 = require("fs/promises");
|
|
@@ -14317,6 +14423,15 @@ function inboxOutputToWire(out) {
|
|
|
14317
14423
|
}))
|
|
14318
14424
|
};
|
|
14319
14425
|
}
|
|
14426
|
+
function runOutputToWire(out) {
|
|
14427
|
+
return {
|
|
14428
|
+
exit_code: out.exitCode,
|
|
14429
|
+
stdout: out.stdout,
|
|
14430
|
+
stderr: out.stderr,
|
|
14431
|
+
redactions: out.redactions,
|
|
14432
|
+
truncated: out.truncated
|
|
14433
|
+
};
|
|
14434
|
+
}
|
|
14320
14435
|
function listOutputToWire(out) {
|
|
14321
14436
|
return {
|
|
14322
14437
|
entries: out.entries.map((entry) => ({
|
|
@@ -14347,7 +14462,7 @@ function proFileGetOutputToWire(out) {
|
|
|
14347
14462
|
}
|
|
14348
14463
|
|
|
14349
14464
|
// src/server.ts
|
|
14350
|
-
var SERVER_VERSION = "0.
|
|
14465
|
+
var SERVER_VERSION = "0.5.0";
|
|
14351
14466
|
var toolMeta = (name) => {
|
|
14352
14467
|
const { title, description, annotations } = seal_mcp_v1_default.tools[name];
|
|
14353
14468
|
return { title, description, annotations };
|
|
@@ -14356,6 +14471,8 @@ var SHARE_INPUT = seal_mcp_v1_default.tools.seal_share.input.properties;
|
|
|
14356
14471
|
var REQUEST_INPUT = seal_mcp_v1_default.tools.seal_request.input.properties;
|
|
14357
14472
|
var OPEN_INPUT = seal_mcp_v1_default.tools.seal_open.input.properties;
|
|
14358
14473
|
var INBOX_INPUT = seal_mcp_v1_default.tools.seal_inbox.input.properties;
|
|
14474
|
+
var RUN_INPUT = seal_mcp_v1_default.tools.seal_run.input.properties;
|
|
14475
|
+
var RUN_SECRET = RUN_INPUT.secrets.items.properties;
|
|
14359
14476
|
var PRO_SECRET_USE = seal_mcp_v1_default.pro_tools.seal_pro_secret_use;
|
|
14360
14477
|
var PRO_SECRET_REQUEST = seal_mcp_v1_default.pro_tools.seal_pro_secret_request;
|
|
14361
14478
|
var PRO_FILE_GET = seal_mcp_v1_default.pro_tools.seal_pro_file_get;
|
|
@@ -14533,6 +14650,29 @@ async function createServer(options) {
|
|
|
14533
14650
|
},
|
|
14534
14651
|
async (args) => wrapTool(() => sealInbox(ctx, { wait: args.wait }), inboxOutputToWire)
|
|
14535
14652
|
);
|
|
14653
|
+
mcp.registerTool(
|
|
14654
|
+
"seal_run",
|
|
14655
|
+
{
|
|
14656
|
+
...toolMeta("seal_run"),
|
|
14657
|
+
inputSchema: {
|
|
14658
|
+
command: external_exports.array(external_exports.string()).min(1).describe(RUN_INPUT.command.description),
|
|
14659
|
+
secrets: external_exports.array(
|
|
14660
|
+
external_exports.object({
|
|
14661
|
+
// Exactly one of `url` and `item`: the handler refuses anything else (contract `one_of`).
|
|
14662
|
+
url: external_exports.string().url().optional().describe(RUN_SECRET.url.description),
|
|
14663
|
+
item: external_exports.string().regex(/^in_/).optional().describe(RUN_SECRET.item.description),
|
|
14664
|
+
name: external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).optional().describe(RUN_SECRET.name.description)
|
|
14665
|
+
})
|
|
14666
|
+
).min(1).max(10).describe(RUN_INPUT.secrets.description),
|
|
14667
|
+
cwd: external_exports.string().optional().describe(RUN_INPUT.cwd.description),
|
|
14668
|
+
timeout: external_exports.number().int().min(1).max(600).default(RUN_INPUT.timeout.default).describe(RUN_INPUT.timeout.description)
|
|
14669
|
+
}
|
|
14670
|
+
},
|
|
14671
|
+
async (args) => wrapTool(
|
|
14672
|
+
() => sealRun(ctx, { command: args.command, secrets: args.secrets, cwd: args.cwd, timeout: args.timeout }),
|
|
14673
|
+
runOutputToWire
|
|
14674
|
+
)
|
|
14675
|
+
);
|
|
14536
14676
|
mcp.registerTool(
|
|
14537
14677
|
"seal_list",
|
|
14538
14678
|
{
|
package/dist/cli.js
CHANGED
|
@@ -11783,7 +11783,7 @@ var seal_mcp_v1_default = {
|
|
|
11783
11783
|
progress: "When a `tools/call` carries `_meta.progressToken`, `seal_share` and `seal_open mode=file` send `notifications/progress` with `progress` = ciphertext bytes done (upload: confirmed by storage) and `total` = ciphertext bytes in all; at most one per second, `progress` strictly increasing, the last one at `progress = total`."
|
|
11784
11784
|
},
|
|
11785
11785
|
server_instructions: {
|
|
11786
|
-
text: "SEAL moves files and secrets between people and agents without their content entering this conversation. When someone wants a file, folder or secret sent to them, to another machine or to another agent, call seal_share with its path and do not open, print or Read it first. When you need an API key, password, file or folder from the user, or they offer or ask how to give you one, call seal_request right away instead of explaining other ways or asking them to paste or upload it. Open a SEAL link someone gave you with seal_open (mode=file for secrets and large files).
|
|
11786
|
+
text: "SEAL moves files and secrets between people and agents without their content entering this conversation. When someone wants a file, folder or secret sent to them, to another machine or to another agent, call seal_share with its path and do not open, print or Read it first. When you need an API key, password, file or folder from the user, or they offer or ask how to give you one, call seal_request right away instead of explaining other ways or asking them to paste or upload it. Open a SEAL link someone gave you with seal_open (mode=file for secrets and large files). Anything sent to you without a link (to your key, your address) waits in seal_inbox: call it, then seal_open item. To run a program with a secret in its environment, use seal_run: the value stays out of the chat. Never read or print a SEAL handoff file.",
|
|
11787
11787
|
note: "The `instructions` of the `initialize` result (SPEC-AGENTS \xA78.1). Hosts that defer MCP tools behind a tool search (Claude Code) show the model only tool names until it searches, but put these instructions into the system prompt: this is the one text a model reads before it decides to look for a tool, or reads the file it was asked to send."
|
|
11788
11788
|
},
|
|
11789
11789
|
tools: {
|
|
@@ -11983,7 +11983,7 @@ var seal_mcp_v1_default = {
|
|
|
11983
11983
|
},
|
|
11984
11984
|
seal_inbox: {
|
|
11985
11985
|
title: "Check the inbox",
|
|
11986
|
-
description: "
|
|
11986
|
+
description: "What other agents or people sent to you: seals addressed to your key (your address), no link needed. Returns handles; open one with seal_open item=<handle>. `wait`: seconds to wait for something new, 0 to 300.",
|
|
11987
11987
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
11988
11988
|
input: {
|
|
11989
11989
|
properties: {
|
|
@@ -12015,6 +12015,46 @@ var seal_mcp_v1_default = {
|
|
|
12015
12015
|
MUST_NOT_contain: ["path", "fragment", "url", "seal_id", "share_code"]
|
|
12016
12016
|
}
|
|
12017
12017
|
},
|
|
12018
|
+
seal_run: {
|
|
12019
|
+
title: "Run a program with secrets",
|
|
12020
|
+
description: "Run a program with secrets from SEAL links or your inbox in its environment. The values never enter this conversation: they are removed from the output you get back. Use it instead of reading a secret file.",
|
|
12021
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
12022
|
+
input: {
|
|
12023
|
+
required: ["command", "secrets"],
|
|
12024
|
+
properties: {
|
|
12025
|
+
command: { type: "array", items: { type: "string" }, minItems: 1, description: "The program and its arguments, run without a shell." },
|
|
12026
|
+
secrets: {
|
|
12027
|
+
type: "array",
|
|
12028
|
+
minItems: 1,
|
|
12029
|
+
maxItems: 10,
|
|
12030
|
+
description: "Each one is a link or an inbox item carrying one text file (up to 64 KiB), put into one variable of the program's environment.",
|
|
12031
|
+
items: {
|
|
12032
|
+
one_of: ["url", "item"],
|
|
12033
|
+
properties: {
|
|
12034
|
+
url: { type: "string", format: "uri", description: "Full SEAL share URL including fragment." },
|
|
12035
|
+
item: { type: "string", pattern: "^in_", description: "An inbox handle from seal_inbox (`in_\u2026`), instead of a URL." },
|
|
12036
|
+
name: { type: "string", pattern: "^[A-Za-z_][A-Za-z0-9_]*$", description: "The variable; default: the file's name in capitals (`Brave API key` \u2192 `BRAVE_API_KEY`)." }
|
|
12037
|
+
}
|
|
12038
|
+
}
|
|
12039
|
+
},
|
|
12040
|
+
cwd: { type: "string", description: "Working directory of the program; default this server's." },
|
|
12041
|
+
timeout: { type: "integer", minimum: 1, maximum: 600, default: 120, description: "Seconds; after them the program is killed." }
|
|
12042
|
+
}
|
|
12043
|
+
},
|
|
12044
|
+
flow: "SPEC-ADDRESS F4. Runs `seal run --redact --json --seal <url>=<NAME> \u2026 --item <inbox id>=<NAME> \u2026 -- <command\u2026>`, the `seal` CLI found on PATH as seal_request finds it, with this server's key as SEAL_SEED in the CLI's environment only and stdin closed. The CLI opens the links and inbox items with that key, removes SEAL_SEED and SEAL_PASSWORD from the program's environment and replaces every value of 8 bytes or more in its output with `[sealed:NAME]`. The values never enter this server's memory (\u04182). After `timeout` the CLI and the program are killed as one process group. Not a sandbox: the host lets the model run commands anyway, and a program that prints a value transformed (base64, one character at a time) escapes the redaction.",
|
|
12045
|
+
output: {
|
|
12046
|
+
type: "object",
|
|
12047
|
+
required: ["exit_code", "stdout", "stderr", "redactions", "truncated"],
|
|
12048
|
+
properties: {
|
|
12049
|
+
exit_code: { type: "integer", description: "The program's; 128 + the signal when a signal killed it." },
|
|
12050
|
+
stdout: { type: "string", description: "The last 64 KiB of the program's stdout, values replaced." },
|
|
12051
|
+
stderr: { type: "string", description: "The last 64 KiB of its stderr, values replaced." },
|
|
12052
|
+
redactions: { type: "integer", minimum: 0, description: "How many values were replaced." },
|
|
12053
|
+
truncated: { type: "boolean", description: "A stream was longer than 64 KiB: only its end is here." }
|
|
12054
|
+
},
|
|
12055
|
+
MUST_NOT_contain: ["value", "secret_value", "content"]
|
|
12056
|
+
}
|
|
12057
|
+
},
|
|
12018
12058
|
seal_list: {
|
|
12019
12059
|
title: "List SEALs created via THIS MCP server",
|
|
12020
12060
|
description: "Seals you created through this server: opaque handles with status and expiry. Seals made with the CLI or the desktop client live elsewhere and are not listed.",
|
|
@@ -12362,6 +12402,10 @@ var seal_mcp_v1_default = {
|
|
|
12362
12402
|
request_cancelled: "seal_request: the user closed the dialog or declined the link. Ask again only if they want to.",
|
|
12363
12403
|
request_expired: "seal_request: nobody answered the link before it expired. Make a new request if still needed.",
|
|
12364
12404
|
request_failed: "seal_request: the local dialog or seal.net failed for a reason other than the above; the message says which.",
|
|
12405
|
+
run_cli_missing: "seal_run: there is no `seal` CLI on PATH. Install it: curl -fsSL https://seal.net/install.sh | sh",
|
|
12406
|
+
run_not_text: "seal_run: a link carries more than one file, or a file over 64 KiB or not UTF-8 text: not a secret for an environment.",
|
|
12407
|
+
run_timeout: "seal_run: the program ran longer than `timeout` and was killed with the CLI.",
|
|
12408
|
+
run_failed: "seal_run: the CLI refused before the program started (a bad or gone link, a password, two secrets for one variable, a program that does not start); the message has the CLI's code.",
|
|
12365
12409
|
pro_secret_not_found: "seal_pro_secret_use: no secret with that id or path in the organisation, or it has no version yet.",
|
|
12366
12410
|
pro_secret_not_granted: "seal_pro_secret_use: the secret exists but this version is not granted to this agent, or the grant was revoked. A person grants it on the secret's page.",
|
|
12367
12411
|
pro_secret_expired: "seal_pro_secret_use: the secret version has expired.",
|
|
@@ -13446,6 +13490,68 @@ async function sealRevoke(ctx, input) {
|
|
|
13446
13490
|
return { revoked: true, revokedAt: now };
|
|
13447
13491
|
}
|
|
13448
13492
|
|
|
13493
|
+
// src/tools/run.ts
|
|
13494
|
+
import { spawn } from "child_process";
|
|
13495
|
+
async function sealRun(ctx, input) {
|
|
13496
|
+
const cli = findSealCli();
|
|
13497
|
+
if (!cli) throw new ToolError("run_cli_missing", "there is no `seal` CLI on PATH. Install it: curl -fsSL https://seal.net/install.sh | sh");
|
|
13498
|
+
const args = ["--json", "run", "--redact"];
|
|
13499
|
+
for (const secret of input.secrets) {
|
|
13500
|
+
if (secret.url === void 0 === (secret.item === void 0)) {
|
|
13501
|
+
throw new ToolError("run_failed", "each secret takes exactly one of `url` (a SEAL link) and `item` (an inbox handle from seal_inbox)");
|
|
13502
|
+
}
|
|
13503
|
+
const name = secret.name === void 0 ? "" : `=${secret.name}`;
|
|
13504
|
+
if (secret.url !== void 0) args.push("--seal", secret.url + name);
|
|
13505
|
+
else args.push("--item", (await inboxItem(ctx, secret.item)).id + name);
|
|
13506
|
+
}
|
|
13507
|
+
args.push("--", ...input.command);
|
|
13508
|
+
const env = { ...process.env, SEAL_SEED: bytesToBase64url(ctx.identity.secret) };
|
|
13509
|
+
const { stdout, timedOut } = await runGroup(cli, args, { cwd: input.cwd, env, timeoutMs: input.timeout * 1e3 });
|
|
13510
|
+
if (timedOut) throw new ToolError("run_timeout", `the program ran longer than ${input.timeout} s and was killed`);
|
|
13511
|
+
let answer;
|
|
13512
|
+
try {
|
|
13513
|
+
answer = JSON.parse(stdout);
|
|
13514
|
+
} catch {
|
|
13515
|
+
throw new ToolError("run_failed", "the `seal` CLI answered no JSON: it is older than 1.4.0 (seal run --redact). Update it: curl -fsSL https://seal.net/install.sh | sh");
|
|
13516
|
+
}
|
|
13517
|
+
if ("error" in answer) {
|
|
13518
|
+
throw new ToolError(answer.error.code === "run.not_text" ? "run_not_text" : "run_failed", `seal run: ${answer.error.code}: ${answer.error.message}`);
|
|
13519
|
+
}
|
|
13520
|
+
return {
|
|
13521
|
+
exitCode: answer.exit_code,
|
|
13522
|
+
stdout: answer.stdout,
|
|
13523
|
+
stderr: answer.stderr,
|
|
13524
|
+
redactions: answer.redactions,
|
|
13525
|
+
truncated: answer.truncated
|
|
13526
|
+
};
|
|
13527
|
+
}
|
|
13528
|
+
function runGroup(cli, args, opts) {
|
|
13529
|
+
const group = process.platform !== "win32";
|
|
13530
|
+
return new Promise((resolve5, reject) => {
|
|
13531
|
+
const child = spawn(cli, args, { cwd: opts.cwd, env: opts.env, stdio: ["ignore", "pipe", "ignore"], detached: group });
|
|
13532
|
+
let stdout = "";
|
|
13533
|
+
let timedOut = false;
|
|
13534
|
+
child.stdout.setEncoding("utf8");
|
|
13535
|
+
child.stdout.on("data", (chunk) => stdout += chunk);
|
|
13536
|
+
const timer = setTimeout(() => {
|
|
13537
|
+
timedOut = true;
|
|
13538
|
+
try {
|
|
13539
|
+
if (group && child.pid !== void 0) process.kill(-child.pid, "SIGKILL");
|
|
13540
|
+
else child.kill("SIGKILL");
|
|
13541
|
+
} catch {
|
|
13542
|
+
}
|
|
13543
|
+
}, opts.timeoutMs);
|
|
13544
|
+
child.on("error", (err2) => {
|
|
13545
|
+
clearTimeout(timer);
|
|
13546
|
+
reject(new ToolError("run_failed", `could not start ${cli}: ${err2.message}`, err2));
|
|
13547
|
+
});
|
|
13548
|
+
child.on("close", () => {
|
|
13549
|
+
clearTimeout(timer);
|
|
13550
|
+
resolve5({ stdout, timedOut });
|
|
13551
|
+
});
|
|
13552
|
+
});
|
|
13553
|
+
}
|
|
13554
|
+
|
|
13449
13555
|
// src/tools/share.ts
|
|
13450
13556
|
import { openAsBlob as openAsBlob2 } from "fs";
|
|
13451
13557
|
import { stat as stat3 } from "fs/promises";
|
|
@@ -14289,6 +14395,15 @@ function inboxOutputToWire(out) {
|
|
|
14289
14395
|
}))
|
|
14290
14396
|
};
|
|
14291
14397
|
}
|
|
14398
|
+
function runOutputToWire(out) {
|
|
14399
|
+
return {
|
|
14400
|
+
exit_code: out.exitCode,
|
|
14401
|
+
stdout: out.stdout,
|
|
14402
|
+
stderr: out.stderr,
|
|
14403
|
+
redactions: out.redactions,
|
|
14404
|
+
truncated: out.truncated
|
|
14405
|
+
};
|
|
14406
|
+
}
|
|
14292
14407
|
function listOutputToWire(out) {
|
|
14293
14408
|
return {
|
|
14294
14409
|
entries: out.entries.map((entry) => ({
|
|
@@ -14319,7 +14434,7 @@ function proFileGetOutputToWire(out) {
|
|
|
14319
14434
|
}
|
|
14320
14435
|
|
|
14321
14436
|
// src/server.ts
|
|
14322
|
-
var SERVER_VERSION = "0.
|
|
14437
|
+
var SERVER_VERSION = "0.5.0";
|
|
14323
14438
|
var toolMeta = (name) => {
|
|
14324
14439
|
const { title, description, annotations } = seal_mcp_v1_default.tools[name];
|
|
14325
14440
|
return { title, description, annotations };
|
|
@@ -14328,6 +14443,8 @@ var SHARE_INPUT = seal_mcp_v1_default.tools.seal_share.input.properties;
|
|
|
14328
14443
|
var REQUEST_INPUT = seal_mcp_v1_default.tools.seal_request.input.properties;
|
|
14329
14444
|
var OPEN_INPUT = seal_mcp_v1_default.tools.seal_open.input.properties;
|
|
14330
14445
|
var INBOX_INPUT = seal_mcp_v1_default.tools.seal_inbox.input.properties;
|
|
14446
|
+
var RUN_INPUT = seal_mcp_v1_default.tools.seal_run.input.properties;
|
|
14447
|
+
var RUN_SECRET = RUN_INPUT.secrets.items.properties;
|
|
14331
14448
|
var PRO_SECRET_USE = seal_mcp_v1_default.pro_tools.seal_pro_secret_use;
|
|
14332
14449
|
var PRO_SECRET_REQUEST = seal_mcp_v1_default.pro_tools.seal_pro_secret_request;
|
|
14333
14450
|
var PRO_FILE_GET = seal_mcp_v1_default.pro_tools.seal_pro_file_get;
|
|
@@ -14505,6 +14622,29 @@ async function createServer(options) {
|
|
|
14505
14622
|
},
|
|
14506
14623
|
async (args) => wrapTool(() => sealInbox(ctx, { wait: args.wait }), inboxOutputToWire)
|
|
14507
14624
|
);
|
|
14625
|
+
mcp.registerTool(
|
|
14626
|
+
"seal_run",
|
|
14627
|
+
{
|
|
14628
|
+
...toolMeta("seal_run"),
|
|
14629
|
+
inputSchema: {
|
|
14630
|
+
command: external_exports.array(external_exports.string()).min(1).describe(RUN_INPUT.command.description),
|
|
14631
|
+
secrets: external_exports.array(
|
|
14632
|
+
external_exports.object({
|
|
14633
|
+
// Exactly one of `url` and `item`: the handler refuses anything else (contract `one_of`).
|
|
14634
|
+
url: external_exports.string().url().optional().describe(RUN_SECRET.url.description),
|
|
14635
|
+
item: external_exports.string().regex(/^in_/).optional().describe(RUN_SECRET.item.description),
|
|
14636
|
+
name: external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).optional().describe(RUN_SECRET.name.description)
|
|
14637
|
+
})
|
|
14638
|
+
).min(1).max(10).describe(RUN_INPUT.secrets.description),
|
|
14639
|
+
cwd: external_exports.string().optional().describe(RUN_INPUT.cwd.description),
|
|
14640
|
+
timeout: external_exports.number().int().min(1).max(600).default(RUN_INPUT.timeout.default).describe(RUN_INPUT.timeout.description)
|
|
14641
|
+
}
|
|
14642
|
+
},
|
|
14643
|
+
async (args) => wrapTool(
|
|
14644
|
+
() => sealRun(ctx, { command: args.command, secrets: args.secrets, cwd: args.cwd, timeout: args.timeout }),
|
|
14645
|
+
runOutputToWire
|
|
14646
|
+
)
|
|
14647
|
+
);
|
|
14508
14648
|
mcp.registerTool(
|
|
14509
14649
|
"seal_list",
|
|
14510
14650
|
{
|
package/dist/index.cjs
CHANGED
|
@@ -4145,7 +4145,7 @@ var seal_mcp_v1_default = {
|
|
|
4145
4145
|
progress: "When a `tools/call` carries `_meta.progressToken`, `seal_share` and `seal_open mode=file` send `notifications/progress` with `progress` = ciphertext bytes done (upload: confirmed by storage) and `total` = ciphertext bytes in all; at most one per second, `progress` strictly increasing, the last one at `progress = total`."
|
|
4146
4146
|
},
|
|
4147
4147
|
server_instructions: {
|
|
4148
|
-
text: "SEAL moves files and secrets between people and agents without their content entering this conversation. When someone wants a file, folder or secret sent to them, to another machine or to another agent, call seal_share with its path and do not open, print or Read it first. When you need an API key, password, file or folder from the user, or they offer or ask how to give you one, call seal_request right away instead of explaining other ways or asking them to paste or upload it. Open a SEAL link someone gave you with seal_open (mode=file for secrets and large files).
|
|
4148
|
+
text: "SEAL moves files and secrets between people and agents without their content entering this conversation. When someone wants a file, folder or secret sent to them, to another machine or to another agent, call seal_share with its path and do not open, print or Read it first. When you need an API key, password, file or folder from the user, or they offer or ask how to give you one, call seal_request right away instead of explaining other ways or asking them to paste or upload it. Open a SEAL link someone gave you with seal_open (mode=file for secrets and large files). Anything sent to you without a link (to your key, your address) waits in seal_inbox: call it, then seal_open item. To run a program with a secret in its environment, use seal_run: the value stays out of the chat. Never read or print a SEAL handoff file.",
|
|
4149
4149
|
note: "The `instructions` of the `initialize` result (SPEC-AGENTS \xA78.1). Hosts that defer MCP tools behind a tool search (Claude Code) show the model only tool names until it searches, but put these instructions into the system prompt: this is the one text a model reads before it decides to look for a tool, or reads the file it was asked to send."
|
|
4150
4150
|
},
|
|
4151
4151
|
tools: {
|
|
@@ -4345,7 +4345,7 @@ var seal_mcp_v1_default = {
|
|
|
4345
4345
|
},
|
|
4346
4346
|
seal_inbox: {
|
|
4347
4347
|
title: "Check the inbox",
|
|
4348
|
-
description: "
|
|
4348
|
+
description: "What other agents or people sent to you: seals addressed to your key (your address), no link needed. Returns handles; open one with seal_open item=<handle>. `wait`: seconds to wait for something new, 0 to 300.",
|
|
4349
4349
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
4350
4350
|
input: {
|
|
4351
4351
|
properties: {
|
|
@@ -4377,6 +4377,46 @@ var seal_mcp_v1_default = {
|
|
|
4377
4377
|
MUST_NOT_contain: ["path", "fragment", "url", "seal_id", "share_code"]
|
|
4378
4378
|
}
|
|
4379
4379
|
},
|
|
4380
|
+
seal_run: {
|
|
4381
|
+
title: "Run a program with secrets",
|
|
4382
|
+
description: "Run a program with secrets from SEAL links or your inbox in its environment. The values never enter this conversation: they are removed from the output you get back. Use it instead of reading a secret file.",
|
|
4383
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
4384
|
+
input: {
|
|
4385
|
+
required: ["command", "secrets"],
|
|
4386
|
+
properties: {
|
|
4387
|
+
command: { type: "array", items: { type: "string" }, minItems: 1, description: "The program and its arguments, run without a shell." },
|
|
4388
|
+
secrets: {
|
|
4389
|
+
type: "array",
|
|
4390
|
+
minItems: 1,
|
|
4391
|
+
maxItems: 10,
|
|
4392
|
+
description: "Each one is a link or an inbox item carrying one text file (up to 64 KiB), put into one variable of the program's environment.",
|
|
4393
|
+
items: {
|
|
4394
|
+
one_of: ["url", "item"],
|
|
4395
|
+
properties: {
|
|
4396
|
+
url: { type: "string", format: "uri", description: "Full SEAL share URL including fragment." },
|
|
4397
|
+
item: { type: "string", pattern: "^in_", description: "An inbox handle from seal_inbox (`in_\u2026`), instead of a URL." },
|
|
4398
|
+
name: { type: "string", pattern: "^[A-Za-z_][A-Za-z0-9_]*$", description: "The variable; default: the file's name in capitals (`Brave API key` \u2192 `BRAVE_API_KEY`)." }
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
},
|
|
4402
|
+
cwd: { type: "string", description: "Working directory of the program; default this server's." },
|
|
4403
|
+
timeout: { type: "integer", minimum: 1, maximum: 600, default: 120, description: "Seconds; after them the program is killed." }
|
|
4404
|
+
}
|
|
4405
|
+
},
|
|
4406
|
+
flow: "SPEC-ADDRESS F4. Runs `seal run --redact --json --seal <url>=<NAME> \u2026 --item <inbox id>=<NAME> \u2026 -- <command\u2026>`, the `seal` CLI found on PATH as seal_request finds it, with this server's key as SEAL_SEED in the CLI's environment only and stdin closed. The CLI opens the links and inbox items with that key, removes SEAL_SEED and SEAL_PASSWORD from the program's environment and replaces every value of 8 bytes or more in its output with `[sealed:NAME]`. The values never enter this server's memory (\u04182). After `timeout` the CLI and the program are killed as one process group. Not a sandbox: the host lets the model run commands anyway, and a program that prints a value transformed (base64, one character at a time) escapes the redaction.",
|
|
4407
|
+
output: {
|
|
4408
|
+
type: "object",
|
|
4409
|
+
required: ["exit_code", "stdout", "stderr", "redactions", "truncated"],
|
|
4410
|
+
properties: {
|
|
4411
|
+
exit_code: { type: "integer", description: "The program's; 128 + the signal when a signal killed it." },
|
|
4412
|
+
stdout: { type: "string", description: "The last 64 KiB of the program's stdout, values replaced." },
|
|
4413
|
+
stderr: { type: "string", description: "The last 64 KiB of its stderr, values replaced." },
|
|
4414
|
+
redactions: { type: "integer", minimum: 0, description: "How many values were replaced." },
|
|
4415
|
+
truncated: { type: "boolean", description: "A stream was longer than 64 KiB: only its end is here." }
|
|
4416
|
+
},
|
|
4417
|
+
MUST_NOT_contain: ["value", "secret_value", "content"]
|
|
4418
|
+
}
|
|
4419
|
+
},
|
|
4380
4420
|
seal_list: {
|
|
4381
4421
|
title: "List SEALs created via THIS MCP server",
|
|
4382
4422
|
description: "Seals you created through this server: opaque handles with status and expiry. Seals made with the CLI or the desktop client live elsewhere and are not listed.",
|
|
@@ -4724,6 +4764,10 @@ var seal_mcp_v1_default = {
|
|
|
4724
4764
|
request_cancelled: "seal_request: the user closed the dialog or declined the link. Ask again only if they want to.",
|
|
4725
4765
|
request_expired: "seal_request: nobody answered the link before it expired. Make a new request if still needed.",
|
|
4726
4766
|
request_failed: "seal_request: the local dialog or seal.net failed for a reason other than the above; the message says which.",
|
|
4767
|
+
run_cli_missing: "seal_run: there is no `seal` CLI on PATH. Install it: curl -fsSL https://seal.net/install.sh | sh",
|
|
4768
|
+
run_not_text: "seal_run: a link carries more than one file, or a file over 64 KiB or not UTF-8 text: not a secret for an environment.",
|
|
4769
|
+
run_timeout: "seal_run: the program ran longer than `timeout` and was killed with the CLI.",
|
|
4770
|
+
run_failed: "seal_run: the CLI refused before the program started (a bad or gone link, a password, two secrets for one variable, a program that does not start); the message has the CLI's code.",
|
|
4727
4771
|
pro_secret_not_found: "seal_pro_secret_use: no secret with that id or path in the organisation, or it has no version yet.",
|
|
4728
4772
|
pro_secret_not_granted: "seal_pro_secret_use: the secret exists but this version is not granted to this agent, or the grant was revoked. A person grants it on the secret's page.",
|
|
4729
4773
|
pro_secret_expired: "seal_pro_secret_use: the secret version has expired.",
|
|
@@ -13274,6 +13318,68 @@ async function sealRevoke(ctx, input) {
|
|
|
13274
13318
|
return { revoked: true, revokedAt: now };
|
|
13275
13319
|
}
|
|
13276
13320
|
|
|
13321
|
+
// src/tools/run.ts
|
|
13322
|
+
var import_node_child_process2 = require("child_process");
|
|
13323
|
+
async function sealRun(ctx, input) {
|
|
13324
|
+
const cli = findSealCli();
|
|
13325
|
+
if (!cli) throw new ToolError("run_cli_missing", "there is no `seal` CLI on PATH. Install it: curl -fsSL https://seal.net/install.sh | sh");
|
|
13326
|
+
const args = ["--json", "run", "--redact"];
|
|
13327
|
+
for (const secret of input.secrets) {
|
|
13328
|
+
if (secret.url === void 0 === (secret.item === void 0)) {
|
|
13329
|
+
throw new ToolError("run_failed", "each secret takes exactly one of `url` (a SEAL link) and `item` (an inbox handle from seal_inbox)");
|
|
13330
|
+
}
|
|
13331
|
+
const name = secret.name === void 0 ? "" : `=${secret.name}`;
|
|
13332
|
+
if (secret.url !== void 0) args.push("--seal", secret.url + name);
|
|
13333
|
+
else args.push("--item", (await inboxItem(ctx, secret.item)).id + name);
|
|
13334
|
+
}
|
|
13335
|
+
args.push("--", ...input.command);
|
|
13336
|
+
const env = { ...process.env, SEAL_SEED: bytesToBase64url(ctx.identity.secret) };
|
|
13337
|
+
const { stdout, timedOut } = await runGroup(cli, args, { cwd: input.cwd, env, timeoutMs: input.timeout * 1e3 });
|
|
13338
|
+
if (timedOut) throw new ToolError("run_timeout", `the program ran longer than ${input.timeout} s and was killed`);
|
|
13339
|
+
let answer;
|
|
13340
|
+
try {
|
|
13341
|
+
answer = JSON.parse(stdout);
|
|
13342
|
+
} catch {
|
|
13343
|
+
throw new ToolError("run_failed", "the `seal` CLI answered no JSON: it is older than 1.4.0 (seal run --redact). Update it: curl -fsSL https://seal.net/install.sh | sh");
|
|
13344
|
+
}
|
|
13345
|
+
if ("error" in answer) {
|
|
13346
|
+
throw new ToolError(answer.error.code === "run.not_text" ? "run_not_text" : "run_failed", `seal run: ${answer.error.code}: ${answer.error.message}`);
|
|
13347
|
+
}
|
|
13348
|
+
return {
|
|
13349
|
+
exitCode: answer.exit_code,
|
|
13350
|
+
stdout: answer.stdout,
|
|
13351
|
+
stderr: answer.stderr,
|
|
13352
|
+
redactions: answer.redactions,
|
|
13353
|
+
truncated: answer.truncated
|
|
13354
|
+
};
|
|
13355
|
+
}
|
|
13356
|
+
function runGroup(cli, args, opts) {
|
|
13357
|
+
const group = process.platform !== "win32";
|
|
13358
|
+
return new Promise((resolve5, reject) => {
|
|
13359
|
+
const child = (0, import_node_child_process2.spawn)(cli, args, { cwd: opts.cwd, env: opts.env, stdio: ["ignore", "pipe", "ignore"], detached: group });
|
|
13360
|
+
let stdout = "";
|
|
13361
|
+
let timedOut = false;
|
|
13362
|
+
child.stdout.setEncoding("utf8");
|
|
13363
|
+
child.stdout.on("data", (chunk) => stdout += chunk);
|
|
13364
|
+
const timer = setTimeout(() => {
|
|
13365
|
+
timedOut = true;
|
|
13366
|
+
try {
|
|
13367
|
+
if (group && child.pid !== void 0) process.kill(-child.pid, "SIGKILL");
|
|
13368
|
+
else child.kill("SIGKILL");
|
|
13369
|
+
} catch {
|
|
13370
|
+
}
|
|
13371
|
+
}, opts.timeoutMs);
|
|
13372
|
+
child.on("error", (err2) => {
|
|
13373
|
+
clearTimeout(timer);
|
|
13374
|
+
reject(new ToolError("run_failed", `could not start ${cli}: ${err2.message}`, err2));
|
|
13375
|
+
});
|
|
13376
|
+
child.on("close", () => {
|
|
13377
|
+
clearTimeout(timer);
|
|
13378
|
+
resolve5({ stdout, timedOut });
|
|
13379
|
+
});
|
|
13380
|
+
});
|
|
13381
|
+
}
|
|
13382
|
+
|
|
13277
13383
|
// src/tools/share.ts
|
|
13278
13384
|
var import_node_fs5 = require("fs");
|
|
13279
13385
|
var import_promises9 = require("fs/promises");
|
|
@@ -14117,6 +14223,15 @@ function inboxOutputToWire(out) {
|
|
|
14117
14223
|
}))
|
|
14118
14224
|
};
|
|
14119
14225
|
}
|
|
14226
|
+
function runOutputToWire(out) {
|
|
14227
|
+
return {
|
|
14228
|
+
exit_code: out.exitCode,
|
|
14229
|
+
stdout: out.stdout,
|
|
14230
|
+
stderr: out.stderr,
|
|
14231
|
+
redactions: out.redactions,
|
|
14232
|
+
truncated: out.truncated
|
|
14233
|
+
};
|
|
14234
|
+
}
|
|
14120
14235
|
function listOutputToWire(out) {
|
|
14121
14236
|
return {
|
|
14122
14237
|
entries: out.entries.map((entry) => ({
|
|
@@ -14147,7 +14262,7 @@ function proFileGetOutputToWire(out) {
|
|
|
14147
14262
|
}
|
|
14148
14263
|
|
|
14149
14264
|
// src/server.ts
|
|
14150
|
-
var SERVER_VERSION = "0.
|
|
14265
|
+
var SERVER_VERSION = "0.5.0";
|
|
14151
14266
|
var toolMeta = (name) => {
|
|
14152
14267
|
const { title, description, annotations } = seal_mcp_v1_default.tools[name];
|
|
14153
14268
|
return { title, description, annotations };
|
|
@@ -14156,6 +14271,8 @@ var SHARE_INPUT = seal_mcp_v1_default.tools.seal_share.input.properties;
|
|
|
14156
14271
|
var REQUEST_INPUT = seal_mcp_v1_default.tools.seal_request.input.properties;
|
|
14157
14272
|
var OPEN_INPUT = seal_mcp_v1_default.tools.seal_open.input.properties;
|
|
14158
14273
|
var INBOX_INPUT = seal_mcp_v1_default.tools.seal_inbox.input.properties;
|
|
14274
|
+
var RUN_INPUT = seal_mcp_v1_default.tools.seal_run.input.properties;
|
|
14275
|
+
var RUN_SECRET = RUN_INPUT.secrets.items.properties;
|
|
14159
14276
|
var PRO_SECRET_USE = seal_mcp_v1_default.pro_tools.seal_pro_secret_use;
|
|
14160
14277
|
var PRO_SECRET_REQUEST = seal_mcp_v1_default.pro_tools.seal_pro_secret_request;
|
|
14161
14278
|
var PRO_FILE_GET = seal_mcp_v1_default.pro_tools.seal_pro_file_get;
|
|
@@ -14333,6 +14450,29 @@ async function createServer(options) {
|
|
|
14333
14450
|
},
|
|
14334
14451
|
async (args) => wrapTool(() => sealInbox(ctx, { wait: args.wait }), inboxOutputToWire)
|
|
14335
14452
|
);
|
|
14453
|
+
mcp.registerTool(
|
|
14454
|
+
"seal_run",
|
|
14455
|
+
{
|
|
14456
|
+
...toolMeta("seal_run"),
|
|
14457
|
+
inputSchema: {
|
|
14458
|
+
command: external_exports.array(external_exports.string()).min(1).describe(RUN_INPUT.command.description),
|
|
14459
|
+
secrets: external_exports.array(
|
|
14460
|
+
external_exports.object({
|
|
14461
|
+
// Exactly one of `url` and `item`: the handler refuses anything else (contract `one_of`).
|
|
14462
|
+
url: external_exports.string().url().optional().describe(RUN_SECRET.url.description),
|
|
14463
|
+
item: external_exports.string().regex(/^in_/).optional().describe(RUN_SECRET.item.description),
|
|
14464
|
+
name: external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).optional().describe(RUN_SECRET.name.description)
|
|
14465
|
+
})
|
|
14466
|
+
).min(1).max(10).describe(RUN_INPUT.secrets.description),
|
|
14467
|
+
cwd: external_exports.string().optional().describe(RUN_INPUT.cwd.description),
|
|
14468
|
+
timeout: external_exports.number().int().min(1).max(600).default(RUN_INPUT.timeout.default).describe(RUN_INPUT.timeout.description)
|
|
14469
|
+
}
|
|
14470
|
+
},
|
|
14471
|
+
async (args) => wrapTool(
|
|
14472
|
+
() => sealRun(ctx, { command: args.command, secrets: args.secrets, cwd: args.cwd, timeout: args.timeout }),
|
|
14473
|
+
runOutputToWire
|
|
14474
|
+
)
|
|
14475
|
+
);
|
|
14336
14476
|
mcp.registerTool(
|
|
14337
14477
|
"seal_list",
|
|
14338
14478
|
{
|
|
@@ -14499,7 +14639,7 @@ function osDefaultDir(env, platform, home) {
|
|
|
14499
14639
|
}
|
|
14500
14640
|
|
|
14501
14641
|
// src/seed.ts
|
|
14502
|
-
var
|
|
14642
|
+
var import_node_child_process3 = require("child_process");
|
|
14503
14643
|
var import_node_crypto4 = require("crypto");
|
|
14504
14644
|
var import_node_fs6 = require("fs");
|
|
14505
14645
|
var import_node_path10 = require("path");
|
|
@@ -14533,7 +14673,7 @@ function keyringEntry(keyring, platform, service, account) {
|
|
|
14533
14673
|
}
|
|
14534
14674
|
function readKeytarLinuxEntry(service, account) {
|
|
14535
14675
|
return new Promise((resolve5) => {
|
|
14536
|
-
(0,
|
|
14676
|
+
(0, import_node_child_process3.execFile)("secret-tool", ["lookup", "service", service, "account", account], { timeout: 5e3 }, (err2, stdout) => {
|
|
14537
14677
|
const value = err2 ? "" : stdout.replace(/\n$/, "");
|
|
14538
14678
|
resolve5(value.length > 0 ? value : null);
|
|
14539
14679
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -840,8 +840,8 @@ interface WorkloadCredentials {
|
|
|
840
840
|
}
|
|
841
841
|
|
|
842
842
|
/**
|
|
843
|
-
* MCP server bootstrap — wires the
|
|
844
|
-
* `seal_request`, `seal_open`, `seal_inbox`, `seal_list`, `seal_revoke`) to a
|
|
843
|
+
* MCP server bootstrap — wires the seven tools (`seal_share`,
|
|
844
|
+
* `seal_request`, `seal_open`, `seal_inbox`, `seal_run`, `seal_list`, `seal_revoke`) to a
|
|
845
845
|
* stdio-transport `McpServer` instance, and `seal_pro_secret_use`,
|
|
846
846
|
* `seal_pro_secret_request` and `seal_pro_file_get` when SEAL Pro workload
|
|
847
847
|
* credentials exist (contract `pro_tools`).
|
package/dist/index.d.ts
CHANGED
|
@@ -840,8 +840,8 @@ interface WorkloadCredentials {
|
|
|
840
840
|
}
|
|
841
841
|
|
|
842
842
|
/**
|
|
843
|
-
* MCP server bootstrap — wires the
|
|
844
|
-
* `seal_request`, `seal_open`, `seal_inbox`, `seal_list`, `seal_revoke`) to a
|
|
843
|
+
* MCP server bootstrap — wires the seven tools (`seal_share`,
|
|
844
|
+
* `seal_request`, `seal_open`, `seal_inbox`, `seal_run`, `seal_list`, `seal_revoke`) to a
|
|
845
845
|
* stdio-transport `McpServer` instance, and `seal_pro_secret_use`,
|
|
846
846
|
* `seal_pro_secret_request` and `seal_pro_file_get` when SEAL Pro workload
|
|
847
847
|
* credentials exist (contract `pro_tools`).
|
package/dist/index.js
CHANGED
|
@@ -4065,7 +4065,7 @@ var seal_mcp_v1_default = {
|
|
|
4065
4065
|
progress: "When a `tools/call` carries `_meta.progressToken`, `seal_share` and `seal_open mode=file` send `notifications/progress` with `progress` = ciphertext bytes done (upload: confirmed by storage) and `total` = ciphertext bytes in all; at most one per second, `progress` strictly increasing, the last one at `progress = total`."
|
|
4066
4066
|
},
|
|
4067
4067
|
server_instructions: {
|
|
4068
|
-
text: "SEAL moves files and secrets between people and agents without their content entering this conversation. When someone wants a file, folder or secret sent to them, to another machine or to another agent, call seal_share with its path and do not open, print or Read it first. When you need an API key, password, file or folder from the user, or they offer or ask how to give you one, call seal_request right away instead of explaining other ways or asking them to paste or upload it. Open a SEAL link someone gave you with seal_open (mode=file for secrets and large files).
|
|
4068
|
+
text: "SEAL moves files and secrets between people and agents without their content entering this conversation. When someone wants a file, folder or secret sent to them, to another machine or to another agent, call seal_share with its path and do not open, print or Read it first. When you need an API key, password, file or folder from the user, or they offer or ask how to give you one, call seal_request right away instead of explaining other ways or asking them to paste or upload it. Open a SEAL link someone gave you with seal_open (mode=file for secrets and large files). Anything sent to you without a link (to your key, your address) waits in seal_inbox: call it, then seal_open item. To run a program with a secret in its environment, use seal_run: the value stays out of the chat. Never read or print a SEAL handoff file.",
|
|
4069
4069
|
note: "The `instructions` of the `initialize` result (SPEC-AGENTS \xA78.1). Hosts that defer MCP tools behind a tool search (Claude Code) show the model only tool names until it searches, but put these instructions into the system prompt: this is the one text a model reads before it decides to look for a tool, or reads the file it was asked to send."
|
|
4070
4070
|
},
|
|
4071
4071
|
tools: {
|
|
@@ -4265,7 +4265,7 @@ var seal_mcp_v1_default = {
|
|
|
4265
4265
|
},
|
|
4266
4266
|
seal_inbox: {
|
|
4267
4267
|
title: "Check the inbox",
|
|
4268
|
-
description: "
|
|
4268
|
+
description: "What other agents or people sent to you: seals addressed to your key (your address), no link needed. Returns handles; open one with seal_open item=<handle>. `wait`: seconds to wait for something new, 0 to 300.",
|
|
4269
4269
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
4270
4270
|
input: {
|
|
4271
4271
|
properties: {
|
|
@@ -4297,6 +4297,46 @@ var seal_mcp_v1_default = {
|
|
|
4297
4297
|
MUST_NOT_contain: ["path", "fragment", "url", "seal_id", "share_code"]
|
|
4298
4298
|
}
|
|
4299
4299
|
},
|
|
4300
|
+
seal_run: {
|
|
4301
|
+
title: "Run a program with secrets",
|
|
4302
|
+
description: "Run a program with secrets from SEAL links or your inbox in its environment. The values never enter this conversation: they are removed from the output you get back. Use it instead of reading a secret file.",
|
|
4303
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
4304
|
+
input: {
|
|
4305
|
+
required: ["command", "secrets"],
|
|
4306
|
+
properties: {
|
|
4307
|
+
command: { type: "array", items: { type: "string" }, minItems: 1, description: "The program and its arguments, run without a shell." },
|
|
4308
|
+
secrets: {
|
|
4309
|
+
type: "array",
|
|
4310
|
+
minItems: 1,
|
|
4311
|
+
maxItems: 10,
|
|
4312
|
+
description: "Each one is a link or an inbox item carrying one text file (up to 64 KiB), put into one variable of the program's environment.",
|
|
4313
|
+
items: {
|
|
4314
|
+
one_of: ["url", "item"],
|
|
4315
|
+
properties: {
|
|
4316
|
+
url: { type: "string", format: "uri", description: "Full SEAL share URL including fragment." },
|
|
4317
|
+
item: { type: "string", pattern: "^in_", description: "An inbox handle from seal_inbox (`in_\u2026`), instead of a URL." },
|
|
4318
|
+
name: { type: "string", pattern: "^[A-Za-z_][A-Za-z0-9_]*$", description: "The variable; default: the file's name in capitals (`Brave API key` \u2192 `BRAVE_API_KEY`)." }
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4321
|
+
},
|
|
4322
|
+
cwd: { type: "string", description: "Working directory of the program; default this server's." },
|
|
4323
|
+
timeout: { type: "integer", minimum: 1, maximum: 600, default: 120, description: "Seconds; after them the program is killed." }
|
|
4324
|
+
}
|
|
4325
|
+
},
|
|
4326
|
+
flow: "SPEC-ADDRESS F4. Runs `seal run --redact --json --seal <url>=<NAME> \u2026 --item <inbox id>=<NAME> \u2026 -- <command\u2026>`, the `seal` CLI found on PATH as seal_request finds it, with this server's key as SEAL_SEED in the CLI's environment only and stdin closed. The CLI opens the links and inbox items with that key, removes SEAL_SEED and SEAL_PASSWORD from the program's environment and replaces every value of 8 bytes or more in its output with `[sealed:NAME]`. The values never enter this server's memory (\u04182). After `timeout` the CLI and the program are killed as one process group. Not a sandbox: the host lets the model run commands anyway, and a program that prints a value transformed (base64, one character at a time) escapes the redaction.",
|
|
4327
|
+
output: {
|
|
4328
|
+
type: "object",
|
|
4329
|
+
required: ["exit_code", "stdout", "stderr", "redactions", "truncated"],
|
|
4330
|
+
properties: {
|
|
4331
|
+
exit_code: { type: "integer", description: "The program's; 128 + the signal when a signal killed it." },
|
|
4332
|
+
stdout: { type: "string", description: "The last 64 KiB of the program's stdout, values replaced." },
|
|
4333
|
+
stderr: { type: "string", description: "The last 64 KiB of its stderr, values replaced." },
|
|
4334
|
+
redactions: { type: "integer", minimum: 0, description: "How many values were replaced." },
|
|
4335
|
+
truncated: { type: "boolean", description: "A stream was longer than 64 KiB: only its end is here." }
|
|
4336
|
+
},
|
|
4337
|
+
MUST_NOT_contain: ["value", "secret_value", "content"]
|
|
4338
|
+
}
|
|
4339
|
+
},
|
|
4300
4340
|
seal_list: {
|
|
4301
4341
|
title: "List SEALs created via THIS MCP server",
|
|
4302
4342
|
description: "Seals you created through this server: opaque handles with status and expiry. Seals made with the CLI or the desktop client live elsewhere and are not listed.",
|
|
@@ -4644,6 +4684,10 @@ var seal_mcp_v1_default = {
|
|
|
4644
4684
|
request_cancelled: "seal_request: the user closed the dialog or declined the link. Ask again only if they want to.",
|
|
4645
4685
|
request_expired: "seal_request: nobody answered the link before it expired. Make a new request if still needed.",
|
|
4646
4686
|
request_failed: "seal_request: the local dialog or seal.net failed for a reason other than the above; the message says which.",
|
|
4687
|
+
run_cli_missing: "seal_run: there is no `seal` CLI on PATH. Install it: curl -fsSL https://seal.net/install.sh | sh",
|
|
4688
|
+
run_not_text: "seal_run: a link carries more than one file, or a file over 64 KiB or not UTF-8 text: not a secret for an environment.",
|
|
4689
|
+
run_timeout: "seal_run: the program ran longer than `timeout` and was killed with the CLI.",
|
|
4690
|
+
run_failed: "seal_run: the CLI refused before the program started (a bad or gone link, a password, two secrets for one variable, a program that does not start); the message has the CLI's code.",
|
|
4647
4691
|
pro_secret_not_found: "seal_pro_secret_use: no secret with that id or path in the organisation, or it has no version yet.",
|
|
4648
4692
|
pro_secret_not_granted: "seal_pro_secret_use: the secret exists but this version is not granted to this agent, or the grant was revoked. A person grants it on the secret's page.",
|
|
4649
4693
|
pro_secret_expired: "seal_pro_secret_use: the secret version has expired.",
|
|
@@ -13194,6 +13238,68 @@ async function sealRevoke(ctx, input) {
|
|
|
13194
13238
|
return { revoked: true, revokedAt: now };
|
|
13195
13239
|
}
|
|
13196
13240
|
|
|
13241
|
+
// src/tools/run.ts
|
|
13242
|
+
import { spawn } from "child_process";
|
|
13243
|
+
async function sealRun(ctx, input) {
|
|
13244
|
+
const cli = findSealCli();
|
|
13245
|
+
if (!cli) throw new ToolError("run_cli_missing", "there is no `seal` CLI on PATH. Install it: curl -fsSL https://seal.net/install.sh | sh");
|
|
13246
|
+
const args = ["--json", "run", "--redact"];
|
|
13247
|
+
for (const secret of input.secrets) {
|
|
13248
|
+
if (secret.url === void 0 === (secret.item === void 0)) {
|
|
13249
|
+
throw new ToolError("run_failed", "each secret takes exactly one of `url` (a SEAL link) and `item` (an inbox handle from seal_inbox)");
|
|
13250
|
+
}
|
|
13251
|
+
const name = secret.name === void 0 ? "" : `=${secret.name}`;
|
|
13252
|
+
if (secret.url !== void 0) args.push("--seal", secret.url + name);
|
|
13253
|
+
else args.push("--item", (await inboxItem(ctx, secret.item)).id + name);
|
|
13254
|
+
}
|
|
13255
|
+
args.push("--", ...input.command);
|
|
13256
|
+
const env = { ...process.env, SEAL_SEED: bytesToBase64url(ctx.identity.secret) };
|
|
13257
|
+
const { stdout, timedOut } = await runGroup(cli, args, { cwd: input.cwd, env, timeoutMs: input.timeout * 1e3 });
|
|
13258
|
+
if (timedOut) throw new ToolError("run_timeout", `the program ran longer than ${input.timeout} s and was killed`);
|
|
13259
|
+
let answer;
|
|
13260
|
+
try {
|
|
13261
|
+
answer = JSON.parse(stdout);
|
|
13262
|
+
} catch {
|
|
13263
|
+
throw new ToolError("run_failed", "the `seal` CLI answered no JSON: it is older than 1.4.0 (seal run --redact). Update it: curl -fsSL https://seal.net/install.sh | sh");
|
|
13264
|
+
}
|
|
13265
|
+
if ("error" in answer) {
|
|
13266
|
+
throw new ToolError(answer.error.code === "run.not_text" ? "run_not_text" : "run_failed", `seal run: ${answer.error.code}: ${answer.error.message}`);
|
|
13267
|
+
}
|
|
13268
|
+
return {
|
|
13269
|
+
exitCode: answer.exit_code,
|
|
13270
|
+
stdout: answer.stdout,
|
|
13271
|
+
stderr: answer.stderr,
|
|
13272
|
+
redactions: answer.redactions,
|
|
13273
|
+
truncated: answer.truncated
|
|
13274
|
+
};
|
|
13275
|
+
}
|
|
13276
|
+
function runGroup(cli, args, opts) {
|
|
13277
|
+
const group = process.platform !== "win32";
|
|
13278
|
+
return new Promise((resolve5, reject) => {
|
|
13279
|
+
const child = spawn(cli, args, { cwd: opts.cwd, env: opts.env, stdio: ["ignore", "pipe", "ignore"], detached: group });
|
|
13280
|
+
let stdout = "";
|
|
13281
|
+
let timedOut = false;
|
|
13282
|
+
child.stdout.setEncoding("utf8");
|
|
13283
|
+
child.stdout.on("data", (chunk) => stdout += chunk);
|
|
13284
|
+
const timer = setTimeout(() => {
|
|
13285
|
+
timedOut = true;
|
|
13286
|
+
try {
|
|
13287
|
+
if (group && child.pid !== void 0) process.kill(-child.pid, "SIGKILL");
|
|
13288
|
+
else child.kill("SIGKILL");
|
|
13289
|
+
} catch {
|
|
13290
|
+
}
|
|
13291
|
+
}, opts.timeoutMs);
|
|
13292
|
+
child.on("error", (err2) => {
|
|
13293
|
+
clearTimeout(timer);
|
|
13294
|
+
reject(new ToolError("run_failed", `could not start ${cli}: ${err2.message}`, err2));
|
|
13295
|
+
});
|
|
13296
|
+
child.on("close", () => {
|
|
13297
|
+
clearTimeout(timer);
|
|
13298
|
+
resolve5({ stdout, timedOut });
|
|
13299
|
+
});
|
|
13300
|
+
});
|
|
13301
|
+
}
|
|
13302
|
+
|
|
13197
13303
|
// src/tools/share.ts
|
|
13198
13304
|
import { openAsBlob as openAsBlob2 } from "fs";
|
|
13199
13305
|
import { stat as stat3 } from "fs/promises";
|
|
@@ -14037,6 +14143,15 @@ function inboxOutputToWire(out) {
|
|
|
14037
14143
|
}))
|
|
14038
14144
|
};
|
|
14039
14145
|
}
|
|
14146
|
+
function runOutputToWire(out) {
|
|
14147
|
+
return {
|
|
14148
|
+
exit_code: out.exitCode,
|
|
14149
|
+
stdout: out.stdout,
|
|
14150
|
+
stderr: out.stderr,
|
|
14151
|
+
redactions: out.redactions,
|
|
14152
|
+
truncated: out.truncated
|
|
14153
|
+
};
|
|
14154
|
+
}
|
|
14040
14155
|
function listOutputToWire(out) {
|
|
14041
14156
|
return {
|
|
14042
14157
|
entries: out.entries.map((entry) => ({
|
|
@@ -14067,7 +14182,7 @@ function proFileGetOutputToWire(out) {
|
|
|
14067
14182
|
}
|
|
14068
14183
|
|
|
14069
14184
|
// src/server.ts
|
|
14070
|
-
var SERVER_VERSION = "0.
|
|
14185
|
+
var SERVER_VERSION = "0.5.0";
|
|
14071
14186
|
var toolMeta = (name) => {
|
|
14072
14187
|
const { title, description, annotations } = seal_mcp_v1_default.tools[name];
|
|
14073
14188
|
return { title, description, annotations };
|
|
@@ -14076,6 +14191,8 @@ var SHARE_INPUT = seal_mcp_v1_default.tools.seal_share.input.properties;
|
|
|
14076
14191
|
var REQUEST_INPUT = seal_mcp_v1_default.tools.seal_request.input.properties;
|
|
14077
14192
|
var OPEN_INPUT = seal_mcp_v1_default.tools.seal_open.input.properties;
|
|
14078
14193
|
var INBOX_INPUT = seal_mcp_v1_default.tools.seal_inbox.input.properties;
|
|
14194
|
+
var RUN_INPUT = seal_mcp_v1_default.tools.seal_run.input.properties;
|
|
14195
|
+
var RUN_SECRET = RUN_INPUT.secrets.items.properties;
|
|
14079
14196
|
var PRO_SECRET_USE = seal_mcp_v1_default.pro_tools.seal_pro_secret_use;
|
|
14080
14197
|
var PRO_SECRET_REQUEST = seal_mcp_v1_default.pro_tools.seal_pro_secret_request;
|
|
14081
14198
|
var PRO_FILE_GET = seal_mcp_v1_default.pro_tools.seal_pro_file_get;
|
|
@@ -14253,6 +14370,29 @@ async function createServer(options) {
|
|
|
14253
14370
|
},
|
|
14254
14371
|
async (args) => wrapTool(() => sealInbox(ctx, { wait: args.wait }), inboxOutputToWire)
|
|
14255
14372
|
);
|
|
14373
|
+
mcp.registerTool(
|
|
14374
|
+
"seal_run",
|
|
14375
|
+
{
|
|
14376
|
+
...toolMeta("seal_run"),
|
|
14377
|
+
inputSchema: {
|
|
14378
|
+
command: external_exports.array(external_exports.string()).min(1).describe(RUN_INPUT.command.description),
|
|
14379
|
+
secrets: external_exports.array(
|
|
14380
|
+
external_exports.object({
|
|
14381
|
+
// Exactly one of `url` and `item`: the handler refuses anything else (contract `one_of`).
|
|
14382
|
+
url: external_exports.string().url().optional().describe(RUN_SECRET.url.description),
|
|
14383
|
+
item: external_exports.string().regex(/^in_/).optional().describe(RUN_SECRET.item.description),
|
|
14384
|
+
name: external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).optional().describe(RUN_SECRET.name.description)
|
|
14385
|
+
})
|
|
14386
|
+
).min(1).max(10).describe(RUN_INPUT.secrets.description),
|
|
14387
|
+
cwd: external_exports.string().optional().describe(RUN_INPUT.cwd.description),
|
|
14388
|
+
timeout: external_exports.number().int().min(1).max(600).default(RUN_INPUT.timeout.default).describe(RUN_INPUT.timeout.description)
|
|
14389
|
+
}
|
|
14390
|
+
},
|
|
14391
|
+
async (args) => wrapTool(
|
|
14392
|
+
() => sealRun(ctx, { command: args.command, secrets: args.secrets, cwd: args.cwd, timeout: args.timeout }),
|
|
14393
|
+
runOutputToWire
|
|
14394
|
+
)
|
|
14395
|
+
);
|
|
14256
14396
|
mcp.registerTool(
|
|
14257
14397
|
"seal_list",
|
|
14258
14398
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sealnet-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"mcpName": "net.seal/mcp",
|
|
5
5
|
"description": "Model Context Protocol server for SEAL: handoff mode puts the link on the user's clipboard or in a 0600 file, never in the chat; explicit opt-in to forward mode with strict TTL/recipient policy.",
|
|
6
6
|
"type": "module",
|