@safekb/mcp 0.10.1 → 0.11.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 +5 -0
- package/dist/safekb-mcp.js +30 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -371,6 +371,11 @@ custom fields and the key name of every custom field (sealed ones too) stay read
|
|
|
371
371
|
space takes its credentials along (a credential's group in the old space is left behind).
|
|
372
372
|
- `reveal_sealed_credential` and `get_sealed_totp` decrypt locally. The hosted `reveal_credential` and
|
|
373
373
|
`get_credential_totp` cannot open a sealed credential.
|
|
374
|
+
- AI access (`llm_visibility`): `visible`, `use_only` or `human_only`. `reveal_sealed_credential` and
|
|
375
|
+
`get_sealed_totp` work only on `visible` credentials. New and migrated sealed credentials start
|
|
376
|
+
`use_only`; only you make one `visible`, in the SafeKB app. `set_sealed_credential_llm_visibility`
|
|
377
|
+
only tightens it. The client enforces this: a client older than 0.11.0 still reveals every sealed
|
|
378
|
+
credential.
|
|
374
379
|
- Visible fields can be edited without the secret. Such an edit is flagged (`visible_tampered`) with the
|
|
375
380
|
changed fields until a signed save accepts it (`accept_visible_changes`).
|
|
376
381
|
|
package/dist/safekb-mcp.js
CHANGED
|
@@ -2581,7 +2581,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
2581
2581
|
// package.json
|
|
2582
2582
|
var package_default = {
|
|
2583
2583
|
name: "@safekb/mcp",
|
|
2584
|
-
version: "0.
|
|
2584
|
+
version: "0.11.0",
|
|
2585
2585
|
description: "SafeKB local MCP client: proxies the hosted SafeKB tools and adds zero-knowledge sealed documents.",
|
|
2586
2586
|
license: "SEE LICENSE IN LICENSE",
|
|
2587
2587
|
author: "Mustafa Öztürk",
|
|
@@ -3276,6 +3276,11 @@ class SealedApi {
|
|
|
3276
3276
|
fetchSealedCredential(id) {
|
|
3277
3277
|
return this.http.getJson(`/credentials/${id}/sealed`);
|
|
3278
3278
|
}
|
|
3279
|
+
setLlmVisibility(id, llmVisibility) {
|
|
3280
|
+
return this.http.putJson(`/credentials/${id}/sealed/llm-visibility`, {
|
|
3281
|
+
llm_visibility: llmVisibility
|
|
3282
|
+
});
|
|
3283
|
+
}
|
|
3279
3284
|
lastAttestedVisibleFields(id) {
|
|
3280
3285
|
return this.http.getJson(`/credentials/${id}/sealed/attested`);
|
|
3281
3286
|
}
|
|
@@ -9289,7 +9294,17 @@ class CredentialAccess {
|
|
|
9289
9294
|
return serializeEnvelope(await wrapDek(keys, credentialId, dek));
|
|
9290
9295
|
}
|
|
9291
9296
|
async open(credentialId) {
|
|
9297
|
+
return this.decrypt(await this.ctx.api.fetchSealedCredential(credentialId));
|
|
9298
|
+
}
|
|
9299
|
+
async openForReveal(credentialId) {
|
|
9292
9300
|
const credential2 = await this.ctx.api.fetchSealedCredential(credentialId);
|
|
9301
|
+
if (credential2.llm_visibility !== "visible") {
|
|
9302
|
+
throw new ClientError(`Credential ${credentialId} (${credential2.title}) is ${credential2.llm_visibility}: its secrets are never shown to an LLM. No tool uses a secret without showing it yet: ask the user to enter it. Only the user can change this, in the SafeKB app.`);
|
|
9303
|
+
}
|
|
9304
|
+
return this.decrypt(credential2);
|
|
9305
|
+
}
|
|
9306
|
+
async decrypt(credential2) {
|
|
9307
|
+
const credentialId = credential2.id;
|
|
9293
9308
|
const { keys } = await this.ctx.session.open(credential2.seal_root_id);
|
|
9294
9309
|
const dek = await unwrapDek(keys, credentialId, parseEnvelope(credential2.enc_dek));
|
|
9295
9310
|
const values = await unsealCredentialFields(dek, this.context(credentialId), credential2.enc_fields);
|
|
@@ -9736,7 +9751,7 @@ class CredentialService {
|
|
|
9736
9751
|
this.access = access;
|
|
9737
9752
|
}
|
|
9738
9753
|
async reveal(credentialId, field) {
|
|
9739
|
-
const open5 = await this.access.
|
|
9754
|
+
const open5 = await this.access.openForReveal(credentialId);
|
|
9740
9755
|
const value = this.valueOf(open5, field);
|
|
9741
9756
|
return {
|
|
9742
9757
|
id: credentialId,
|
|
@@ -9747,7 +9762,7 @@ class CredentialService {
|
|
|
9747
9762
|
};
|
|
9748
9763
|
}
|
|
9749
9764
|
async totp(credentialId, nowMs = Date.now()) {
|
|
9750
|
-
const open5 = await this.access.
|
|
9765
|
+
const open5 = await this.access.openForReveal(credentialId);
|
|
9751
9766
|
const code = await totp(parseTotp(this.valueOf(open5, "totp")), nowMs);
|
|
9752
9767
|
return {
|
|
9753
9768
|
id: credentialId,
|
|
@@ -9756,6 +9771,10 @@ class CredentialService {
|
|
|
9756
9771
|
...await this.attestation(open5.credential)
|
|
9757
9772
|
};
|
|
9758
9773
|
}
|
|
9774
|
+
async setLlmVisibility(credentialId, llmVisibility) {
|
|
9775
|
+
const credential2 = await this.ctx.api.setLlmVisibility(credentialId, llmVisibility);
|
|
9776
|
+
return { id: credential2.id, llm_visibility: credential2.llm_visibility };
|
|
9777
|
+
}
|
|
9759
9778
|
valueOf(open5, field) {
|
|
9760
9779
|
const { values, credential: credential2 } = open5;
|
|
9761
9780
|
const value = field.startsWith(CUSTOM_PREFIX2) ? values.custom.find((item) => item.key === field.slice(CUSTOM_PREFIX2.length))?.value : SEALED_CREDENTIAL_FIELDS.includes(field) ? values[field] : undefined;
|
|
@@ -35750,12 +35769,16 @@ var sealedFields = {
|
|
|
35750
35769
|
};
|
|
35751
35770
|
function credentialTools(credentials, migration, names) {
|
|
35752
35771
|
return [
|
|
35753
|
-
defineTool("reveal_sealed_credential", `Decrypt one sealed field of a sealed credential on this machine. The server only sees that the ciphertext was fetched. Find credentials and their visible fields with the standard credential tools (search_credentials, get_credential). ${EXPOSURE_NOTE}`, {
|
|
35772
|
+
defineTool("reveal_sealed_credential", `Decrypt one sealed field of a sealed credential on this machine. The server only sees that the ciphertext was fetched. Find credentials and their visible fields with the standard credential tools (search_credentials, get_credential). Refused unless its llm_visibility is visible. ${EXPOSURE_NOTE}`, {
|
|
35754
35773
|
credential_id: credentialId,
|
|
35755
35774
|
field: exports_external.string().min(1).describe("password, totp, sealed_notes, or custom:{key}.")
|
|
35756
35775
|
}, (args) => credentials.reveal(args.credential_id, args.field)),
|
|
35757
|
-
defineTool("get_sealed_totp", `Compute the current TOTP code of a sealed credential on this machine, without revealing the seed. ${EXPOSURE_NOTE}`, { credential_id: credentialId }, (args) => credentials.totp(args.credential_id)),
|
|
35758
|
-
defineTool("
|
|
35776
|
+
defineTool("get_sealed_totp", `Compute the current TOTP code of a sealed credential on this machine, without revealing the seed. Refused unless its llm_visibility is visible. ${EXPOSURE_NOTE}`, { credential_id: credentialId }, (args) => credentials.totp(args.credential_id)),
|
|
35777
|
+
defineTool("set_sealed_credential_llm_visibility", "Make a sealed credential's secrets less visible to LLMs: use_only (never revealed to an LLM) or human_only (never revealed, entered by a person). It only tightens: human_only cannot go back to use_only from here, and only the user makes a credential visible, in the SafeKB app. The value is not signed and does not change the credential's version.", {
|
|
35778
|
+
credential_id: credentialId,
|
|
35779
|
+
llm_visibility: exports_external.enum(["use_only", "human_only"]).describe("human_only is the more restrictive.")
|
|
35780
|
+
}, (args) => credentials.setLlmVisibility(args.credential_id, args.llm_visibility)),
|
|
35781
|
+
defineTool("insert_sealed_credential", `Create a sealed credential in a seal root. It is kept in the space of that root. It starts use_only: its secrets cannot be revealed to an LLM until the user makes it visible in the SafeKB app. password, totp, sealed_notes and the values of sealed custom fields are encrypted on this machine; title, username, url, tags, notes, visible custom fields and the key name of every custom field (sealed ones too) stay readable by the server so search and duplicate detection work. ${SECRET_ARGUMENT_NOTE}`, {
|
|
35759
35782
|
root_id: rootRef("The seal root that will own it."),
|
|
35760
35783
|
title: exports_external.string().min(1).describe("Visible to the server."),
|
|
35761
35784
|
folder_id: exports_external.string().min(1).describe("Group it belongs to.").optional(),
|
|
@@ -35946,6 +35969,7 @@ The keys of this machine, and backups, are handled by tools that never take or r
|
|
|
35946
35969
|
A credential with \`sealed: true\` belongs to a seal root. Its title, username, url, tags, notes and visible custom fields are readable by the server: find it with the standard credential tools. Its password, TOTP seed, sealed_notes and sealed custom fields open only here: reveal_sealed_credential, get_sealed_totp, insert_sealed_credential, update_sealed_credential.
|
|
35947
35970
|
|
|
35948
35971
|
- Reveal a secret only when the task needs it, and prefer get_sealed_totp over revealing a TOTP seed.
|
|
35972
|
+
- llm_visibility: reveal_sealed_credential and get_sealed_totp work only on visible credentials; on use_only and human_only ones, ask the user to enter the secret. New sealed credentials start use_only, and only the user makes one visible, in the SafeKB app.
|
|
35949
35973
|
- visible_tampered means the visible fields were changed without the key since the last sealed save. Do not trust the url or username of such a credential until the user has reviewed the change.`;
|
|
35950
35974
|
function createSealedTools(ctx) {
|
|
35951
35975
|
const services = createSealedServices(ctx);
|
package/package.json
CHANGED