@saihm/mcp-server-pro 0.1.1 → 0.1.2

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/dist/client.d.ts CHANGED
@@ -60,6 +60,11 @@ export interface ShareGrant {
60
60
  scope?: 'read' | 'write' | 'readwrite';
61
61
  expiryEpoch?: bigint | null;
62
62
  }
63
+ export interface SharedReadGrant {
64
+ sharerPinnedAgentIdHashHex: string;
65
+ sharerRecord: WireIdentityRecord;
66
+ cellId: string;
67
+ }
63
68
  export interface SaihmProClientOpts {
64
69
  tier?: string;
65
70
  seqStatePath?: string;
@@ -88,6 +93,7 @@ export declare class SaihmProClient {
88
93
  status(): Promise<StatusSnapshot>;
89
94
  share(grant: ShareGrant): Promise<ShareResult>;
90
95
  revokeShare(cellId: string, recipientHex: string): Promise<RevokeResult>;
96
+ recallShared(grant: SharedReadGrant): Promise<RecalledCell | null>;
91
97
  governancePropose(args: {
92
98
  scope: 'emission_param' | 'protocol_upgrade';
93
99
  paramKey: string | null;
package/dist/client.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { randomBytes } from 'node:crypto';
2
2
  import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
3
3
  import { dirname } from 'node:path';
4
- import { deriveIdentity, sealCell, openCell, shareCell, encodeEnvelope, decodeEnvelope, encodeShareEnvelope, encodeIdentityRecord, decodeIdentityRecord, fromHex, toHex, utf8, fromUtf8, ctEqual, SeqHighWaterMark, } from '@saihm/client-pro';
4
+ import { deriveIdentity, sealCell, openCell, shareCell, decodeShareEnvelope, unwrapSharedDek, verifyShareSig, openCellWithDek, verifyEnvelope, verifyIdentityRecord, encodeEnvelope, decodeEnvelope, encodeShareEnvelope, encodeIdentityRecord, decodeIdentityRecord, fromHex, toHex, utf8, fromUtf8, ctEqual, SeqHighWaterMark, } from '@saihm/client-pro';
5
5
  const REQUEST_TIMEOUT_MS = 30_000;
6
6
  const MAX_RESPONSE_BYTES = 16 * 1024 * 1024;
7
7
  const MAX_SEQ = (1n << 64n) - 1n;
@@ -378,6 +378,83 @@ export class SaihmProClient {
378
378
  async revokeShare(cellId, recipientHex) {
379
379
  return this.call('saihm_revoke_share', { cellId, recipient: recipientHex });
380
380
  }
381
+ async recallShared(grant) {
382
+ let sharerRecord;
383
+ let sharerPinned;
384
+ try {
385
+ sharerRecord = decodeIdentityRecord(grant.sharerRecord);
386
+ sharerPinned = fromHex(grant.sharerPinnedAgentIdHashHex);
387
+ }
388
+ catch {
389
+ throw new SaihmEndpointError(0, 'bad_sharer', 'sharer identity record or pinned agentIdHash is malformed');
390
+ }
391
+ verifyIdentityRecord(sharerRecord, sharerPinned);
392
+ const sharerHex = toHex(sharerPinned);
393
+ const r = await this.call('saihm_recall', { sharer: sharerHex, cellId: grant.cellId });
394
+ if (typeof r !== 'object' || r === null || Array.isArray(r)) {
395
+ throw new SaihmEndpointError(502, 'malformed_response', 'endpoint returned a malformed shared-recall response');
396
+ }
397
+ const res = r;
398
+ if (!res.found || !res.wire || !res.contentWire)
399
+ return null;
400
+ let share;
401
+ try {
402
+ share = decodeShareEnvelope(res.wire);
403
+ }
404
+ catch {
405
+ throw new SaihmEndpointError(502, 'malformed_share', `endpoint returned a malformed share envelope for cell '${grant.cellId}'`);
406
+ }
407
+ if (!ctEqual(share.recipientAgentIdHash, this.identity.agentIdHash)) {
408
+ throw new SaihmEndpointError(502, 'foreign_share', 'endpoint returned a share addressed to a different recipient');
409
+ }
410
+ if (!ctEqual(share.sharerAgentIdHash, sharerPinned) || share.cellId !== grant.cellId) {
411
+ throw new SaihmEndpointError(502, 'share_mismatch', `endpoint returned a share for the wrong sharer/cell`);
412
+ }
413
+ if (!verifyShareSig(share, sharerRecord.mldsaPubKey)) {
414
+ throw new SaihmEndpointError(502, 'bad_share_sig', 'share envelope signature does not verify against the pinned sharer key');
415
+ }
416
+ let dek;
417
+ try {
418
+ dek = unwrapSharedDek({
419
+ share,
420
+ recipientAgentIdHash: this.identity.agentIdHash,
421
+ sharerPinnedMldsaPubKey: sharerRecord.mldsaPubKey,
422
+ recipientMlkemSecretKey: this.identity.mlkemSecretKey,
423
+ });
424
+ }
425
+ catch {
426
+ throw new SaihmEndpointError(502, 'undecryptable_share', `share DEK for cell '${grant.cellId}' could not be unwrapped with this identity`);
427
+ }
428
+ try {
429
+ let env;
430
+ try {
431
+ env = decodeEnvelope(res.contentWire);
432
+ }
433
+ catch {
434
+ throw new SaihmEndpointError(502, 'malformed_envelope', `endpoint returned a malformed content envelope for cell '${grant.cellId}'`);
435
+ }
436
+ if (!ctEqual(env.agentIdHash, sharerPinned)) {
437
+ throw new SaihmEndpointError(502, 'foreign_envelope', 'shared content envelope is not signed by the pinned sharer');
438
+ }
439
+ if (env.cellId !== grant.cellId) {
440
+ throw new SaihmEndpointError(502, 'cell_mismatch', `endpoint returned cell '${env.cellId}' for requested '${grant.cellId}'`);
441
+ }
442
+ if (!verifyEnvelope(env)) {
443
+ throw new SaihmEndpointError(502, 'unverified_envelope', `shared content envelope for cell '${grant.cellId}' failed verification`);
444
+ }
445
+ let plaintext;
446
+ try {
447
+ plaintext = fromUtf8(openCellWithDek(env, dek));
448
+ }
449
+ catch {
450
+ throw new SaihmEndpointError(502, 'undecryptable', `shared cell '${grant.cellId}' could not be opened with the unwrapped DEK`);
451
+ }
452
+ return { cellId: env.cellId, plaintext, seq: env.seq.toString(10), commitmentHash: toHex(env.publicMeta.commitmentHash) };
453
+ }
454
+ finally {
455
+ dek.fill(0);
456
+ }
457
+ }
381
458
  async governancePropose(args) {
382
459
  await this.call('saihm_governance_propose', args);
383
460
  throw new SaihmEndpointError(403, 'governance_unavailable', 'governance unavailable');
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { SaihmProClient, SaihmEndpointError } from './client.js';
2
- export type { RememberResult, RecalledCell, ForgetResult, StatusSnapshot, ShareResult, RevokeResult, RememberOpts, ShareGrant, SaihmProClientOpts, } from './client.js';
2
+ export { KeySubstitutionError } from '@saihm/client-pro';
3
+ export type { RememberResult, RecalledCell, ForgetResult, StatusSnapshot, ShareResult, RevokeResult, RememberOpts, ShareGrant, SharedReadGrant, SaihmProClientOpts, } from './client.js';
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export { SaihmProClient, SaihmEndpointError } from './client.js';
2
+ export { KeySubstitutionError } from '@saihm/client-pro';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saihm/mcp-server-pro",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "SAIHM production thin-client. Seals client-side via @saihm/client-pro (ML-DSA-65 identity, per-cell AES-256-GCM DEK wrapped under a client KEK, ML-KEM-768 authenticated sharing) and POSTs opaque ciphertext to the blind, non-custodial SAIHM /mcp endpoint. The master secret, KEK, and plaintext never leave this process. Apache-2.0.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",