sealnet-mcp 0.2.2 → 0.2.4
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 -1
- package/dist/cli.cjs +44 -12
- package/dist/cli.js +44 -12
- package/dist/index.cjs +40 -8
- package/dist/index.d.cts +6 -7
- package/dist/index.d.ts +6 -7
- package/dist/index.js +40 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ All state lives encrypted at `${XDG_CONFIG_HOME}/seal-mcp/state.json` (mode `0o6
|
|
|
35
35
|
|
|
36
36
|
Passphrase delivery, in priority order:
|
|
37
37
|
|
|
38
|
-
1. **OS keychain** (default) — `
|
|
38
|
+
1. **OS keychain** (default) — `@napi-rs/keyring` (optional dependency) reads from the Secret Service on Linux / macOS Keychain / Windows Credential Manager. The first `serve` stores a random passphrase there itself; `sealnet-mcp init` lets you choose one instead.
|
|
39
39
|
2. **`SEAL_MCP_PASSPHRASE` env var** — explicit opt-in for CI/headless. Warning logged.
|
|
40
40
|
3. **Cleartext on disk** — explicit refusal even if user tries `--passphrase-file …`.
|
|
41
41
|
|
package/dist/cli.cjs
CHANGED
|
@@ -7099,6 +7099,7 @@ async function probeStateLock(stateDir, deps = {}) {
|
|
|
7099
7099
|
}
|
|
7100
7100
|
|
|
7101
7101
|
// src/passphrase.ts
|
|
7102
|
+
var import_node_child_process = require("child_process");
|
|
7102
7103
|
var import_node_crypto2 = require("crypto");
|
|
7103
7104
|
var SERVICE_NAME = "seal-mcp";
|
|
7104
7105
|
var ACCOUNT_NAME = "state";
|
|
@@ -7122,23 +7123,50 @@ var PassphraseNotFoundError = class extends Error {
|
|
|
7122
7123
|
this.name = "PassphraseNotFoundError";
|
|
7123
7124
|
}
|
|
7124
7125
|
};
|
|
7126
|
+
function keyringEntry(keyring, platform, service, account) {
|
|
7127
|
+
return platform === "win32" ? keyring.AsyncEntry.withTarget(`${service}/${account}`, service, account) : new keyring.AsyncEntry(service, account, { linux: { store: "secret-service" } });
|
|
7128
|
+
}
|
|
7129
|
+
function readKeytarLinuxEntry(service, account) {
|
|
7130
|
+
return new Promise((resolve5) => {
|
|
7131
|
+
(0, import_node_child_process.execFile)("secret-tool", ["lookup", "service", service, "account", account], { timeout: 5e3 }, (err2, stdout) => {
|
|
7132
|
+
const value = err2 ? "" : stdout.replace(/\n$/, "");
|
|
7133
|
+
resolve5(value.length > 0 ? value : null);
|
|
7134
|
+
});
|
|
7135
|
+
});
|
|
7136
|
+
}
|
|
7137
|
+
function keytarOverKeyring(keyring, platform = process.platform, readLegacy = readKeytarLinuxEntry) {
|
|
7138
|
+
const entry = (service, account) => keyringEntry(keyring, platform, service, account);
|
|
7139
|
+
const store = (service, account, password) => entry(service, account).setSecret(Buffer.from(password, "utf8"));
|
|
7140
|
+
return {
|
|
7141
|
+
async getPassword(service, account) {
|
|
7142
|
+
const secret = await entry(service, account).getSecret();
|
|
7143
|
+
if (secret !== void 0) return Buffer.from(secret).toString("utf8");
|
|
7144
|
+
const legacy = platform === "linux" ? await readLegacy(service, account) : null;
|
|
7145
|
+
if (legacy !== null) await store(service, account, legacy).catch(() => void 0);
|
|
7146
|
+
return legacy;
|
|
7147
|
+
},
|
|
7148
|
+
setPassword: store,
|
|
7149
|
+
deletePassword: (service, account) => entry(service, account).deleteCredential()
|
|
7150
|
+
};
|
|
7151
|
+
}
|
|
7125
7152
|
var keytarCache = void 0;
|
|
7126
7153
|
async function loadKeytar() {
|
|
7127
7154
|
if (keytarCache !== void 0) return keytarCache;
|
|
7128
7155
|
try {
|
|
7129
|
-
const mod = await import("
|
|
7130
|
-
|
|
7131
|
-
|
|
7156
|
+
const mod = await import("@napi-rs/keyring");
|
|
7157
|
+
const keyring = mod.default ?? mod;
|
|
7158
|
+
keyringEntry(keyring, process.platform, SERVICE_NAME, ACCOUNT_NAME);
|
|
7159
|
+
keytarCache = keytarOverKeyring(keyring);
|
|
7132
7160
|
} catch {
|
|
7133
7161
|
keytarCache = null;
|
|
7134
|
-
return null;
|
|
7135
7162
|
}
|
|
7163
|
+
return keytarCache;
|
|
7136
7164
|
}
|
|
7137
7165
|
async function loadKeytarOrThrow() {
|
|
7138
7166
|
const k = await loadKeytar();
|
|
7139
7167
|
if (k === null) {
|
|
7140
7168
|
throw new KeytarUnavailableError(
|
|
7141
|
-
"
|
|
7169
|
+
"no OS keychain. On Linux start a Secret Service (gnome-keyring or KWallet); on macOS/Windows this should never happen \u2014 file a bug."
|
|
7142
7170
|
);
|
|
7143
7171
|
}
|
|
7144
7172
|
return k;
|
|
@@ -11342,6 +11370,10 @@ var seal_mcp_v1_default = {
|
|
|
11342
11370
|
server_info: "name `sealnet-mcp` (the npm package and its binary); version = `package.json::version`, injected at build time (tsup `define`), never a source literal. The state directory, the keychain service and the handoff directory keep the name `seal-mcp`: they are where existing state lives.",
|
|
11343
11371
|
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`."
|
|
11344
11372
|
},
|
|
11373
|
+
server_instructions: {
|
|
11374
|
+
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).",
|
|
11375
|
+
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."
|
|
11376
|
+
},
|
|
11345
11377
|
tools: {
|
|
11346
11378
|
seal_share: {
|
|
11347
11379
|
title: "Share a file via SEAL",
|
|
@@ -11793,7 +11825,7 @@ var seal_mcp_v1_default = {
|
|
|
11793
11825
|
{
|
|
11794
11826
|
rank: 1,
|
|
11795
11827
|
method: "os_keychain",
|
|
11796
|
-
implementation: "
|
|
11828
|
+
implementation: "@napi-rs/keyring (the Secret Service on Linux, never the kernel keyring, which forgets on reboot; macOS Keychain; Windows Credential Manager), an OPTIONAL dependency loaded lazily: its absence never fails install or start. It replaced keytar in 0.2.4 and finds the entry keytar wrote (Windows credential `seal-mcp/state`, the passphrase as UTF-8 bytes; on Linux the old entry is read once through `secret-tool` and stored anew). Service id: 'seal-mcp', account: 'state'. Set during `sealnet-mcp init` (chosen by the user), or by the first `sealnet-mcp serve` without state, which generates a random 32-byte passphrase itself \u2014 no prompt, no `init` step. Read on every subsequent process start.",
|
|
11797
11829
|
default: true
|
|
11798
11830
|
},
|
|
11799
11831
|
{
|
|
@@ -12941,7 +12973,7 @@ async function sealProFileGet(source, input) {
|
|
|
12941
12973
|
}
|
|
12942
12974
|
|
|
12943
12975
|
// src/tools/request.ts
|
|
12944
|
-
var
|
|
12976
|
+
var import_node_child_process2 = require("child_process");
|
|
12945
12977
|
var import_node_fs3 = require("fs");
|
|
12946
12978
|
var import_promises5 = require("fs/promises");
|
|
12947
12979
|
var import_node_os5 = require("os");
|
|
@@ -12960,7 +12992,7 @@ function runSealCli(args) {
|
|
|
12960
12992
|
const cli = findSealCli();
|
|
12961
12993
|
if (!cli) return Promise.resolve(null);
|
|
12962
12994
|
return new Promise((resolve5) => {
|
|
12963
|
-
(0,
|
|
12995
|
+
(0, import_node_child_process2.execFile)(cli, [...args], { maxBuffer: 1024 * 1024 }, (_err, stdout) => {
|
|
12964
12996
|
try {
|
|
12965
12997
|
resolve5(JSON.parse(stdout));
|
|
12966
12998
|
} catch {
|
|
@@ -13933,7 +13965,7 @@ function proFileGetOutputToWire(out) {
|
|
|
13933
13965
|
}
|
|
13934
13966
|
|
|
13935
13967
|
// src/server.ts
|
|
13936
|
-
var SERVER_VERSION = "0.2.
|
|
13968
|
+
var SERVER_VERSION = "0.2.4";
|
|
13937
13969
|
var toolMeta = (name) => {
|
|
13938
13970
|
const { title, description, annotations } = seal_mcp_v1_default.tools[name];
|
|
13939
13971
|
return { title, description, annotations };
|
|
@@ -14001,7 +14033,7 @@ async function createServer(options) {
|
|
|
14001
14033
|
};
|
|
14002
14034
|
const mcp = new import_mcp.McpServer(
|
|
14003
14035
|
{ name: "sealnet-mcp", version: SERVER_VERSION },
|
|
14004
|
-
{ capabilities: { tools: {} } }
|
|
14036
|
+
{ capabilities: { tools: {} }, instructions: seal_mcp_v1_default.server_instructions.text }
|
|
14005
14037
|
);
|
|
14006
14038
|
const disableClipboard = process.env.SEAL_MCP_DISABLE_CLIPBOARD === "1";
|
|
14007
14039
|
mcp.registerTool(
|
|
@@ -14309,7 +14341,7 @@ async function runInit(opts) {
|
|
|
14309
14341
|
if (err2 instanceof KeytarUnavailableError) {
|
|
14310
14342
|
logStderr(`${PROGRAM_NAME} init: warning: ${err2.message}`);
|
|
14311
14343
|
logStderr(
|
|
14312
|
-
`${PROGRAM_NAME} init: state.json IS encrypted, but you must export ${PASSPHRASE_ENV_VAR} before every \`${PROGRAM_NAME} serve\` until
|
|
14344
|
+
`${PROGRAM_NAME} init: state.json IS encrypted, but you must export ${PASSPHRASE_ENV_VAR} before every \`${PROGRAM_NAME} serve\` until the OS keychain works.`
|
|
14313
14345
|
);
|
|
14314
14346
|
} else {
|
|
14315
14347
|
throw err2;
|
|
@@ -14483,7 +14515,7 @@ async function runDoctor(opts) {
|
|
|
14483
14515
|
}
|
|
14484
14516
|
if (!kc.available) {
|
|
14485
14517
|
logStderr(
|
|
14486
|
-
`hint: keychain unavailable.
|
|
14518
|
+
`hint: keychain unavailable. On Linux start a Secret Service (gnome-keyring or KWallet), or rely on ${PASSPHRASE_ENV_VAR}; with neither, \`serve\` runs ephemeral (handles live until restart).`
|
|
14487
14519
|
);
|
|
14488
14520
|
} else if (stateExists && !kc.hasEntry && !report.env_passphrase_set) {
|
|
14489
14521
|
logStderr(`hint: keychain has no entry and ${PASSPHRASE_ENV_VAR} is unset \u2014 \`serve\` will fail.`);
|
package/dist/cli.js
CHANGED
|
@@ -7071,6 +7071,7 @@ async function probeStateLock(stateDir, deps = {}) {
|
|
|
7071
7071
|
}
|
|
7072
7072
|
|
|
7073
7073
|
// src/passphrase.ts
|
|
7074
|
+
import { execFile } from "child_process";
|
|
7074
7075
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
7075
7076
|
var SERVICE_NAME = "seal-mcp";
|
|
7076
7077
|
var ACCOUNT_NAME = "state";
|
|
@@ -7094,23 +7095,50 @@ var PassphraseNotFoundError = class extends Error {
|
|
|
7094
7095
|
this.name = "PassphraseNotFoundError";
|
|
7095
7096
|
}
|
|
7096
7097
|
};
|
|
7098
|
+
function keyringEntry(keyring, platform, service, account) {
|
|
7099
|
+
return platform === "win32" ? keyring.AsyncEntry.withTarget(`${service}/${account}`, service, account) : new keyring.AsyncEntry(service, account, { linux: { store: "secret-service" } });
|
|
7100
|
+
}
|
|
7101
|
+
function readKeytarLinuxEntry(service, account) {
|
|
7102
|
+
return new Promise((resolve5) => {
|
|
7103
|
+
execFile("secret-tool", ["lookup", "service", service, "account", account], { timeout: 5e3 }, (err2, stdout) => {
|
|
7104
|
+
const value = err2 ? "" : stdout.replace(/\n$/, "");
|
|
7105
|
+
resolve5(value.length > 0 ? value : null);
|
|
7106
|
+
});
|
|
7107
|
+
});
|
|
7108
|
+
}
|
|
7109
|
+
function keytarOverKeyring(keyring, platform = process.platform, readLegacy = readKeytarLinuxEntry) {
|
|
7110
|
+
const entry = (service, account) => keyringEntry(keyring, platform, service, account);
|
|
7111
|
+
const store = (service, account, password) => entry(service, account).setSecret(Buffer.from(password, "utf8"));
|
|
7112
|
+
return {
|
|
7113
|
+
async getPassword(service, account) {
|
|
7114
|
+
const secret = await entry(service, account).getSecret();
|
|
7115
|
+
if (secret !== void 0) return Buffer.from(secret).toString("utf8");
|
|
7116
|
+
const legacy = platform === "linux" ? await readLegacy(service, account) : null;
|
|
7117
|
+
if (legacy !== null) await store(service, account, legacy).catch(() => void 0);
|
|
7118
|
+
return legacy;
|
|
7119
|
+
},
|
|
7120
|
+
setPassword: store,
|
|
7121
|
+
deletePassword: (service, account) => entry(service, account).deleteCredential()
|
|
7122
|
+
};
|
|
7123
|
+
}
|
|
7097
7124
|
var keytarCache = void 0;
|
|
7098
7125
|
async function loadKeytar() {
|
|
7099
7126
|
if (keytarCache !== void 0) return keytarCache;
|
|
7100
7127
|
try {
|
|
7101
|
-
const mod = await import("
|
|
7102
|
-
|
|
7103
|
-
|
|
7128
|
+
const mod = await import("@napi-rs/keyring");
|
|
7129
|
+
const keyring = mod.default ?? mod;
|
|
7130
|
+
keyringEntry(keyring, process.platform, SERVICE_NAME, ACCOUNT_NAME);
|
|
7131
|
+
keytarCache = keytarOverKeyring(keyring);
|
|
7104
7132
|
} catch {
|
|
7105
7133
|
keytarCache = null;
|
|
7106
|
-
return null;
|
|
7107
7134
|
}
|
|
7135
|
+
return keytarCache;
|
|
7108
7136
|
}
|
|
7109
7137
|
async function loadKeytarOrThrow() {
|
|
7110
7138
|
const k = await loadKeytar();
|
|
7111
7139
|
if (k === null) {
|
|
7112
7140
|
throw new KeytarUnavailableError(
|
|
7113
|
-
"
|
|
7141
|
+
"no OS keychain. On Linux start a Secret Service (gnome-keyring or KWallet); on macOS/Windows this should never happen \u2014 file a bug."
|
|
7114
7142
|
);
|
|
7115
7143
|
}
|
|
7116
7144
|
return k;
|
|
@@ -11314,6 +11342,10 @@ var seal_mcp_v1_default = {
|
|
|
11314
11342
|
server_info: "name `sealnet-mcp` (the npm package and its binary); version = `package.json::version`, injected at build time (tsup `define`), never a source literal. The state directory, the keychain service and the handoff directory keep the name `seal-mcp`: they are where existing state lives.",
|
|
11315
11343
|
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`."
|
|
11316
11344
|
},
|
|
11345
|
+
server_instructions: {
|
|
11346
|
+
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).",
|
|
11347
|
+
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."
|
|
11348
|
+
},
|
|
11317
11349
|
tools: {
|
|
11318
11350
|
seal_share: {
|
|
11319
11351
|
title: "Share a file via SEAL",
|
|
@@ -11765,7 +11797,7 @@ var seal_mcp_v1_default = {
|
|
|
11765
11797
|
{
|
|
11766
11798
|
rank: 1,
|
|
11767
11799
|
method: "os_keychain",
|
|
11768
|
-
implementation: "
|
|
11800
|
+
implementation: "@napi-rs/keyring (the Secret Service on Linux, never the kernel keyring, which forgets on reboot; macOS Keychain; Windows Credential Manager), an OPTIONAL dependency loaded lazily: its absence never fails install or start. It replaced keytar in 0.2.4 and finds the entry keytar wrote (Windows credential `seal-mcp/state`, the passphrase as UTF-8 bytes; on Linux the old entry is read once through `secret-tool` and stored anew). Service id: 'seal-mcp', account: 'state'. Set during `sealnet-mcp init` (chosen by the user), or by the first `sealnet-mcp serve` without state, which generates a random 32-byte passphrase itself \u2014 no prompt, no `init` step. Read on every subsequent process start.",
|
|
11769
11801
|
default: true
|
|
11770
11802
|
},
|
|
11771
11803
|
{
|
|
@@ -12913,7 +12945,7 @@ async function sealProFileGet(source, input) {
|
|
|
12913
12945
|
}
|
|
12914
12946
|
|
|
12915
12947
|
// src/tools/request.ts
|
|
12916
|
-
import { execFile } from "child_process";
|
|
12948
|
+
import { execFile as execFile2 } from "child_process";
|
|
12917
12949
|
import { existsSync } from "fs";
|
|
12918
12950
|
import { mkdtemp as mkdtemp2 } from "fs/promises";
|
|
12919
12951
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -12932,7 +12964,7 @@ function runSealCli(args) {
|
|
|
12932
12964
|
const cli = findSealCli();
|
|
12933
12965
|
if (!cli) return Promise.resolve(null);
|
|
12934
12966
|
return new Promise((resolve5) => {
|
|
12935
|
-
|
|
12967
|
+
execFile2(cli, [...args], { maxBuffer: 1024 * 1024 }, (_err, stdout) => {
|
|
12936
12968
|
try {
|
|
12937
12969
|
resolve5(JSON.parse(stdout));
|
|
12938
12970
|
} catch {
|
|
@@ -13905,7 +13937,7 @@ function proFileGetOutputToWire(out) {
|
|
|
13905
13937
|
}
|
|
13906
13938
|
|
|
13907
13939
|
// src/server.ts
|
|
13908
|
-
var SERVER_VERSION = "0.2.
|
|
13940
|
+
var SERVER_VERSION = "0.2.4";
|
|
13909
13941
|
var toolMeta = (name) => {
|
|
13910
13942
|
const { title, description, annotations } = seal_mcp_v1_default.tools[name];
|
|
13911
13943
|
return { title, description, annotations };
|
|
@@ -13973,7 +14005,7 @@ async function createServer(options) {
|
|
|
13973
14005
|
};
|
|
13974
14006
|
const mcp = new McpServer(
|
|
13975
14007
|
{ name: "sealnet-mcp", version: SERVER_VERSION },
|
|
13976
|
-
{ capabilities: { tools: {} } }
|
|
14008
|
+
{ capabilities: { tools: {} }, instructions: seal_mcp_v1_default.server_instructions.text }
|
|
13977
14009
|
);
|
|
13978
14010
|
const disableClipboard = process.env.SEAL_MCP_DISABLE_CLIPBOARD === "1";
|
|
13979
14011
|
mcp.registerTool(
|
|
@@ -14281,7 +14313,7 @@ async function runInit(opts) {
|
|
|
14281
14313
|
if (err2 instanceof KeytarUnavailableError) {
|
|
14282
14314
|
logStderr(`${PROGRAM_NAME} init: warning: ${err2.message}`);
|
|
14283
14315
|
logStderr(
|
|
14284
|
-
`${PROGRAM_NAME} init: state.json IS encrypted, but you must export ${PASSPHRASE_ENV_VAR} before every \`${PROGRAM_NAME} serve\` until
|
|
14316
|
+
`${PROGRAM_NAME} init: state.json IS encrypted, but you must export ${PASSPHRASE_ENV_VAR} before every \`${PROGRAM_NAME} serve\` until the OS keychain works.`
|
|
14285
14317
|
);
|
|
14286
14318
|
} else {
|
|
14287
14319
|
throw err2;
|
|
@@ -14455,7 +14487,7 @@ async function runDoctor(opts) {
|
|
|
14455
14487
|
}
|
|
14456
14488
|
if (!kc.available) {
|
|
14457
14489
|
logStderr(
|
|
14458
|
-
`hint: keychain unavailable.
|
|
14490
|
+
`hint: keychain unavailable. On Linux start a Secret Service (gnome-keyring or KWallet), or rely on ${PASSPHRASE_ENV_VAR}; with neither, \`serve\` runs ephemeral (handles live until restart).`
|
|
14459
14491
|
);
|
|
14460
14492
|
} else if (stateExists && !kc.hasEntry && !report.env_passphrase_set) {
|
|
14461
14493
|
logStderr(`hint: keychain has no entry and ${PASSPHRASE_ENV_VAR} is unset \u2014 \`serve\` will fail.`);
|
package/dist/index.cjs
CHANGED
|
@@ -4137,6 +4137,10 @@ var seal_mcp_v1_default = {
|
|
|
4137
4137
|
server_info: "name `sealnet-mcp` (the npm package and its binary); version = `package.json::version`, injected at build time (tsup `define`), never a source literal. The state directory, the keychain service and the handoff directory keep the name `seal-mcp`: they are where existing state lives.",
|
|
4138
4138
|
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`."
|
|
4139
4139
|
},
|
|
4140
|
+
server_instructions: {
|
|
4141
|
+
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).",
|
|
4142
|
+
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."
|
|
4143
|
+
},
|
|
4140
4144
|
tools: {
|
|
4141
4145
|
seal_share: {
|
|
4142
4146
|
title: "Share a file via SEAL",
|
|
@@ -4588,7 +4592,7 @@ var seal_mcp_v1_default = {
|
|
|
4588
4592
|
{
|
|
4589
4593
|
rank: 1,
|
|
4590
4594
|
method: "os_keychain",
|
|
4591
|
-
implementation: "
|
|
4595
|
+
implementation: "@napi-rs/keyring (the Secret Service on Linux, never the kernel keyring, which forgets on reboot; macOS Keychain; Windows Credential Manager), an OPTIONAL dependency loaded lazily: its absence never fails install or start. It replaced keytar in 0.2.4 and finds the entry keytar wrote (Windows credential `seal-mcp/state`, the passphrase as UTF-8 bytes; on Linux the old entry is read once through `secret-tool` and stored anew). Service id: 'seal-mcp', account: 'state'. Set during `sealnet-mcp init` (chosen by the user), or by the first `sealnet-mcp serve` without state, which generates a random 32-byte passphrase itself \u2014 no prompt, no `init` step. Read on every subsequent process start.",
|
|
4592
4596
|
default: true
|
|
4593
4597
|
},
|
|
4594
4598
|
{
|
|
@@ -13788,7 +13792,7 @@ function proFileGetOutputToWire(out) {
|
|
|
13788
13792
|
}
|
|
13789
13793
|
|
|
13790
13794
|
// src/server.ts
|
|
13791
|
-
var SERVER_VERSION = "0.2.
|
|
13795
|
+
var SERVER_VERSION = "0.2.4";
|
|
13792
13796
|
var toolMeta = (name) => {
|
|
13793
13797
|
const { title, description, annotations } = seal_mcp_v1_default.tools[name];
|
|
13794
13798
|
return { title, description, annotations };
|
|
@@ -13856,7 +13860,7 @@ async function createServer(options) {
|
|
|
13856
13860
|
};
|
|
13857
13861
|
const mcp = new import_mcp.McpServer(
|
|
13858
13862
|
{ name: "sealnet-mcp", version: SERVER_VERSION },
|
|
13859
|
-
{ capabilities: { tools: {} } }
|
|
13863
|
+
{ capabilities: { tools: {} }, instructions: seal_mcp_v1_default.server_instructions.text }
|
|
13860
13864
|
);
|
|
13861
13865
|
const disableClipboard = process.env.SEAL_MCP_DISABLE_CLIPBOARD === "1";
|
|
13862
13866
|
mcp.registerTool(
|
|
@@ -14137,6 +14141,7 @@ function osDefaultDir(env, platform, home) {
|
|
|
14137
14141
|
}
|
|
14138
14142
|
|
|
14139
14143
|
// src/passphrase.ts
|
|
14144
|
+
var import_node_child_process2 = require("child_process");
|
|
14140
14145
|
var import_node_crypto4 = require("crypto");
|
|
14141
14146
|
var SERVICE_NAME = "seal-mcp";
|
|
14142
14147
|
var ACCOUNT_NAME = "state";
|
|
@@ -14160,23 +14165,50 @@ var PassphraseNotFoundError = class extends Error {
|
|
|
14160
14165
|
this.name = "PassphraseNotFoundError";
|
|
14161
14166
|
}
|
|
14162
14167
|
};
|
|
14168
|
+
function keyringEntry(keyring, platform, service, account) {
|
|
14169
|
+
return platform === "win32" ? keyring.AsyncEntry.withTarget(`${service}/${account}`, service, account) : new keyring.AsyncEntry(service, account, { linux: { store: "secret-service" } });
|
|
14170
|
+
}
|
|
14171
|
+
function readKeytarLinuxEntry(service, account) {
|
|
14172
|
+
return new Promise((resolve5) => {
|
|
14173
|
+
(0, import_node_child_process2.execFile)("secret-tool", ["lookup", "service", service, "account", account], { timeout: 5e3 }, (err2, stdout) => {
|
|
14174
|
+
const value = err2 ? "" : stdout.replace(/\n$/, "");
|
|
14175
|
+
resolve5(value.length > 0 ? value : null);
|
|
14176
|
+
});
|
|
14177
|
+
});
|
|
14178
|
+
}
|
|
14179
|
+
function keytarOverKeyring(keyring, platform = process.platform, readLegacy = readKeytarLinuxEntry) {
|
|
14180
|
+
const entry = (service, account) => keyringEntry(keyring, platform, service, account);
|
|
14181
|
+
const store = (service, account, password) => entry(service, account).setSecret(Buffer.from(password, "utf8"));
|
|
14182
|
+
return {
|
|
14183
|
+
async getPassword(service, account) {
|
|
14184
|
+
const secret = await entry(service, account).getSecret();
|
|
14185
|
+
if (secret !== void 0) return Buffer.from(secret).toString("utf8");
|
|
14186
|
+
const legacy = platform === "linux" ? await readLegacy(service, account) : null;
|
|
14187
|
+
if (legacy !== null) await store(service, account, legacy).catch(() => void 0);
|
|
14188
|
+
return legacy;
|
|
14189
|
+
},
|
|
14190
|
+
setPassword: store,
|
|
14191
|
+
deletePassword: (service, account) => entry(service, account).deleteCredential()
|
|
14192
|
+
};
|
|
14193
|
+
}
|
|
14163
14194
|
var keytarCache = void 0;
|
|
14164
14195
|
async function loadKeytar() {
|
|
14165
14196
|
if (keytarCache !== void 0) return keytarCache;
|
|
14166
14197
|
try {
|
|
14167
|
-
const mod = await import("
|
|
14168
|
-
|
|
14169
|
-
|
|
14198
|
+
const mod = await import("@napi-rs/keyring");
|
|
14199
|
+
const keyring = mod.default ?? mod;
|
|
14200
|
+
keyringEntry(keyring, process.platform, SERVICE_NAME, ACCOUNT_NAME);
|
|
14201
|
+
keytarCache = keytarOverKeyring(keyring);
|
|
14170
14202
|
} catch {
|
|
14171
14203
|
keytarCache = null;
|
|
14172
|
-
return null;
|
|
14173
14204
|
}
|
|
14205
|
+
return keytarCache;
|
|
14174
14206
|
}
|
|
14175
14207
|
async function loadKeytarOrThrow() {
|
|
14176
14208
|
const k = await loadKeytar();
|
|
14177
14209
|
if (k === null) {
|
|
14178
14210
|
throw new KeytarUnavailableError(
|
|
14179
|
-
"
|
|
14211
|
+
"no OS keychain. On Linux start a Secret Service (gnome-keyring or KWallet); on macOS/Windows this should never happen \u2014 file a bug."
|
|
14180
14212
|
);
|
|
14181
14213
|
}
|
|
14182
14214
|
return k;
|
package/dist/index.d.cts
CHANGED
|
@@ -882,7 +882,7 @@ declare function resolveStateDir(opts?: ResolveOptions): PathResolution;
|
|
|
882
882
|
*
|
|
883
883
|
* Priority chain (per `shared/contracts/seal_mcp.v1.json::passphrase_delivery`):
|
|
884
884
|
*
|
|
885
|
-
* 1. OS keychain via `
|
|
885
|
+
* 1. OS keychain via `@napi-rs/keyring` (service `'seal-mcp'`, account `'state'`).
|
|
886
886
|
* 2. `SEAL_MCP_PASSPHRASE` environment variable. Logs a warning to
|
|
887
887
|
* stderr because env vars are visible via `/proc/<pid>/environ`
|
|
888
888
|
* on Linux, `ps -E`/`procstat -e` on BSD, and via the process
|
|
@@ -890,10 +890,9 @@ declare function resolveStateDir(opts?: ResolveOptions): PathResolution;
|
|
|
890
890
|
* 3. Cleartext on disk — REFUSED. Listed only to document the
|
|
891
891
|
* refusal; the resolver has no codepath that reads from a file.
|
|
892
892
|
*
|
|
893
|
-
*
|
|
894
|
-
* `handoff.ts`) so
|
|
895
|
-
*
|
|
896
|
-
* at import time.
|
|
893
|
+
* The keychain module is an optional dependency loaded lazily (same pattern
|
|
894
|
+
* as `clipboardy` in `handoff.ts`), so a machine without it, or without a
|
|
895
|
+
* keychain, falls back to the env var without crashing at import time.
|
|
897
896
|
*/
|
|
898
897
|
declare const KEYCHAIN_SERVICE: "seal-mcp";
|
|
899
898
|
declare const KEYCHAIN_ACCOUNT: "state";
|
|
@@ -904,8 +903,8 @@ interface ResolvedPassphrase {
|
|
|
904
903
|
readonly source: PassphraseSource;
|
|
905
904
|
}
|
|
906
905
|
/**
|
|
907
|
-
* Surface used by `passphrase.ts` — kept narrow on purpose
|
|
908
|
-
* stub
|
|
906
|
+
* Surface used by `passphrase.ts` — `keytar`'s shape, kept narrow on purpose
|
|
907
|
+
* so tests can stub the keychain without pulling in the native module.
|
|
909
908
|
*/
|
|
910
909
|
interface KeytarLike {
|
|
911
910
|
getPassword(service: string, account: string): Promise<string | null>;
|
package/dist/index.d.ts
CHANGED
|
@@ -882,7 +882,7 @@ declare function resolveStateDir(opts?: ResolveOptions): PathResolution;
|
|
|
882
882
|
*
|
|
883
883
|
* Priority chain (per `shared/contracts/seal_mcp.v1.json::passphrase_delivery`):
|
|
884
884
|
*
|
|
885
|
-
* 1. OS keychain via `
|
|
885
|
+
* 1. OS keychain via `@napi-rs/keyring` (service `'seal-mcp'`, account `'state'`).
|
|
886
886
|
* 2. `SEAL_MCP_PASSPHRASE` environment variable. Logs a warning to
|
|
887
887
|
* stderr because env vars are visible via `/proc/<pid>/environ`
|
|
888
888
|
* on Linux, `ps -E`/`procstat -e` on BSD, and via the process
|
|
@@ -890,10 +890,9 @@ declare function resolveStateDir(opts?: ResolveOptions): PathResolution;
|
|
|
890
890
|
* 3. Cleartext on disk — REFUSED. Listed only to document the
|
|
891
891
|
* refusal; the resolver has no codepath that reads from a file.
|
|
892
892
|
*
|
|
893
|
-
*
|
|
894
|
-
* `handoff.ts`) so
|
|
895
|
-
*
|
|
896
|
-
* at import time.
|
|
893
|
+
* The keychain module is an optional dependency loaded lazily (same pattern
|
|
894
|
+
* as `clipboardy` in `handoff.ts`), so a machine without it, or without a
|
|
895
|
+
* keychain, falls back to the env var without crashing at import time.
|
|
897
896
|
*/
|
|
898
897
|
declare const KEYCHAIN_SERVICE: "seal-mcp";
|
|
899
898
|
declare const KEYCHAIN_ACCOUNT: "state";
|
|
@@ -904,8 +903,8 @@ interface ResolvedPassphrase {
|
|
|
904
903
|
readonly source: PassphraseSource;
|
|
905
904
|
}
|
|
906
905
|
/**
|
|
907
|
-
* Surface used by `passphrase.ts` — kept narrow on purpose
|
|
908
|
-
* stub
|
|
906
|
+
* Surface used by `passphrase.ts` — `keytar`'s shape, kept narrow on purpose
|
|
907
|
+
* so tests can stub the keychain without pulling in the native module.
|
|
909
908
|
*/
|
|
910
909
|
interface KeytarLike {
|
|
911
910
|
getPassword(service: string, account: string): Promise<string | null>;
|
package/dist/index.js
CHANGED
|
@@ -4065,6 +4065,10 @@ var seal_mcp_v1_default = {
|
|
|
4065
4065
|
server_info: "name `sealnet-mcp` (the npm package and its binary); version = `package.json::version`, injected at build time (tsup `define`), never a source literal. The state directory, the keychain service and the handoff directory keep the name `seal-mcp`: they are where existing state lives.",
|
|
4066
4066
|
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`."
|
|
4067
4067
|
},
|
|
4068
|
+
server_instructions: {
|
|
4069
|
+
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).",
|
|
4070
|
+
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."
|
|
4071
|
+
},
|
|
4068
4072
|
tools: {
|
|
4069
4073
|
seal_share: {
|
|
4070
4074
|
title: "Share a file via SEAL",
|
|
@@ -4516,7 +4520,7 @@ var seal_mcp_v1_default = {
|
|
|
4516
4520
|
{
|
|
4517
4521
|
rank: 1,
|
|
4518
4522
|
method: "os_keychain",
|
|
4519
|
-
implementation: "
|
|
4523
|
+
implementation: "@napi-rs/keyring (the Secret Service on Linux, never the kernel keyring, which forgets on reboot; macOS Keychain; Windows Credential Manager), an OPTIONAL dependency loaded lazily: its absence never fails install or start. It replaced keytar in 0.2.4 and finds the entry keytar wrote (Windows credential `seal-mcp/state`, the passphrase as UTF-8 bytes; on Linux the old entry is read once through `secret-tool` and stored anew). Service id: 'seal-mcp', account: 'state'. Set during `sealnet-mcp init` (chosen by the user), or by the first `sealnet-mcp serve` without state, which generates a random 32-byte passphrase itself \u2014 no prompt, no `init` step. Read on every subsequent process start.",
|
|
4520
4524
|
default: true
|
|
4521
4525
|
},
|
|
4522
4526
|
{
|
|
@@ -13716,7 +13720,7 @@ function proFileGetOutputToWire(out) {
|
|
|
13716
13720
|
}
|
|
13717
13721
|
|
|
13718
13722
|
// src/server.ts
|
|
13719
|
-
var SERVER_VERSION = "0.2.
|
|
13723
|
+
var SERVER_VERSION = "0.2.4";
|
|
13720
13724
|
var toolMeta = (name) => {
|
|
13721
13725
|
const { title, description, annotations } = seal_mcp_v1_default.tools[name];
|
|
13722
13726
|
return { title, description, annotations };
|
|
@@ -13784,7 +13788,7 @@ async function createServer(options) {
|
|
|
13784
13788
|
};
|
|
13785
13789
|
const mcp = new McpServer(
|
|
13786
13790
|
{ name: "sealnet-mcp", version: SERVER_VERSION },
|
|
13787
|
-
{ capabilities: { tools: {} } }
|
|
13791
|
+
{ capabilities: { tools: {} }, instructions: seal_mcp_v1_default.server_instructions.text }
|
|
13788
13792
|
);
|
|
13789
13793
|
const disableClipboard = process.env.SEAL_MCP_DISABLE_CLIPBOARD === "1";
|
|
13790
13794
|
mcp.registerTool(
|
|
@@ -14065,6 +14069,7 @@ function osDefaultDir(env, platform, home) {
|
|
|
14065
14069
|
}
|
|
14066
14070
|
|
|
14067
14071
|
// src/passphrase.ts
|
|
14072
|
+
import { execFile as execFile2 } from "child_process";
|
|
14068
14073
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
14069
14074
|
var SERVICE_NAME = "seal-mcp";
|
|
14070
14075
|
var ACCOUNT_NAME = "state";
|
|
@@ -14088,23 +14093,50 @@ var PassphraseNotFoundError = class extends Error {
|
|
|
14088
14093
|
this.name = "PassphraseNotFoundError";
|
|
14089
14094
|
}
|
|
14090
14095
|
};
|
|
14096
|
+
function keyringEntry(keyring, platform, service, account) {
|
|
14097
|
+
return platform === "win32" ? keyring.AsyncEntry.withTarget(`${service}/${account}`, service, account) : new keyring.AsyncEntry(service, account, { linux: { store: "secret-service" } });
|
|
14098
|
+
}
|
|
14099
|
+
function readKeytarLinuxEntry(service, account) {
|
|
14100
|
+
return new Promise((resolve5) => {
|
|
14101
|
+
execFile2("secret-tool", ["lookup", "service", service, "account", account], { timeout: 5e3 }, (err2, stdout) => {
|
|
14102
|
+
const value = err2 ? "" : stdout.replace(/\n$/, "");
|
|
14103
|
+
resolve5(value.length > 0 ? value : null);
|
|
14104
|
+
});
|
|
14105
|
+
});
|
|
14106
|
+
}
|
|
14107
|
+
function keytarOverKeyring(keyring, platform = process.platform, readLegacy = readKeytarLinuxEntry) {
|
|
14108
|
+
const entry = (service, account) => keyringEntry(keyring, platform, service, account);
|
|
14109
|
+
const store = (service, account, password) => entry(service, account).setSecret(Buffer.from(password, "utf8"));
|
|
14110
|
+
return {
|
|
14111
|
+
async getPassword(service, account) {
|
|
14112
|
+
const secret = await entry(service, account).getSecret();
|
|
14113
|
+
if (secret !== void 0) return Buffer.from(secret).toString("utf8");
|
|
14114
|
+
const legacy = platform === "linux" ? await readLegacy(service, account) : null;
|
|
14115
|
+
if (legacy !== null) await store(service, account, legacy).catch(() => void 0);
|
|
14116
|
+
return legacy;
|
|
14117
|
+
},
|
|
14118
|
+
setPassword: store,
|
|
14119
|
+
deletePassword: (service, account) => entry(service, account).deleteCredential()
|
|
14120
|
+
};
|
|
14121
|
+
}
|
|
14091
14122
|
var keytarCache = void 0;
|
|
14092
14123
|
async function loadKeytar() {
|
|
14093
14124
|
if (keytarCache !== void 0) return keytarCache;
|
|
14094
14125
|
try {
|
|
14095
|
-
const mod = await import("
|
|
14096
|
-
|
|
14097
|
-
|
|
14126
|
+
const mod = await import("@napi-rs/keyring");
|
|
14127
|
+
const keyring = mod.default ?? mod;
|
|
14128
|
+
keyringEntry(keyring, process.platform, SERVICE_NAME, ACCOUNT_NAME);
|
|
14129
|
+
keytarCache = keytarOverKeyring(keyring);
|
|
14098
14130
|
} catch {
|
|
14099
14131
|
keytarCache = null;
|
|
14100
|
-
return null;
|
|
14101
14132
|
}
|
|
14133
|
+
return keytarCache;
|
|
14102
14134
|
}
|
|
14103
14135
|
async function loadKeytarOrThrow() {
|
|
14104
14136
|
const k = await loadKeytar();
|
|
14105
14137
|
if (k === null) {
|
|
14106
14138
|
throw new KeytarUnavailableError(
|
|
14107
|
-
"
|
|
14139
|
+
"no OS keychain. On Linux start a Secret Service (gnome-keyring or KWallet); on macOS/Windows this should never happen \u2014 file a bug."
|
|
14108
14140
|
);
|
|
14109
14141
|
}
|
|
14110
14142
|
return k;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sealnet-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"mcpName": "net.seal/mcp",
|
|
5
5
|
"description": "Model Context Protocol server for SEAL — model never holds capability by default (handoff mode); explicit opt-in to forward mode with strict TTL/recipient policy.",
|
|
6
6
|
"type": "module",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"commander": "^14.0.3"
|
|
60
60
|
},
|
|
61
61
|
"optionalDependencies": {
|
|
62
|
-
"
|
|
62
|
+
"@napi-rs/keyring": "^2.1.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@types/node": "^20.10.0",
|