beeos-claw 0.6.49-staging.2 → 0.6.49-staging.3
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
import{RUNTIME_MESSAGE_TYPES}from"../runtime/runtime-message-channel-contract.js";export class McpCredentialRevokedError extends Error{credentialRef;constructor(e){super("MCP credential is revoked"),this.credentialRef=e}}export class McpDesiredStateController{store;projection;credentials;references;releases;receiptHistory;logger;tail=Promise.resolve();unsubscribe;started=!1;revoked=new Map;activeCredentialRevisions=new Map;projections=new Map;credentialServers=new Map;liveReleaseKeys=new Map;desiredRevision="0";authorityEpoch;authorityGeneration=0;authorityCloseBlocked=!1;constructor(e,t,i,r,s,n,a){this.store=e,this.projection=t,this.credentials=i,this.references=r,this.releases=s,this.receiptHistory=n,this.logger=a,this.authorityEpoch=i?.currentAuthority()?.runtimeEpoch??null}setRuntimeReceiptHistoryPort(e){this.receiptHistory=e}async start(e){await this.exclusive(async()=>{this.started||(this.started=!0,this.authorityEpoch=this.credentials?.currentAuthority()?.runtimeEpoch??null,this.unsubscribe=this.credentials?.subscribe(e=>this.acceptCredentialEvent(e)));const t=await this.store.load();await this.reconcileAll(e,t),await this.collectLocalGarbage(void 0,!0)})}async reconnect(e){await this.exclusive(async()=>{await this.reconcileAll(e)})}async list(){return await this.exclusive(async()=>{const e=await this.store.load();this.rememberDesired(e);const t=new Map(this.projection.list().map(e=>[e.name,e]));return{revision:e.revision,servers:e.servers.map(i=>{const r=t.get(i.name);return Object.freeze({...i,...void 0!==r?.connected?{connected:r.connected}:{},tools:r?.tools??Object.freeze([]),projection:this.projections.get(i.name)??pending(e.revision)})})}})}async set(e,t){return await this.exclusive(async()=>await this.store.transaction(async i=>{const r=await i.set(e);return e.credentialRef&&this.references?.owns(e.credentialRef)&&await this.references.commit(e.credentialRef).catch(()=>{}),this.rememberDesired(r),this.activeCredentialRevisions.delete(e.name),this.liveReleaseKeys.delete(e.name),await this.projection.unset(e.name),this.projections.set(e.name,pending(r.revision)),await this.recordDesiredStateCommit(r,e.name,t),await this.collectLocalGarbage(i),r}))}async unset(e,t){return await this.exclusive(async()=>await this.store.transaction(async i=>{const r=await i.unset(e);return this.rememberDesired(r),this.activeCredentialRevisions.delete(e),this.liveReleaseKeys.delete(e),await this.projection.unset(e),this.projections.set(e,pending(r.revision)),await this.recordDesiredStateCommit(r,e,t),await this.collectLocalGarbage(i),r}))}async isCredentialReferenced(e){return await this.exclusive(async()=>(await this.store.load()).servers.some(t=>t.credentialRef===e))}async removeLocalCredentialIfUnreferenced(e){await this.exclusive(async()=>{this.references?.owns(e)&&await this.store.transaction(async t=>{(await t.load()).servers.some(t=>t.credentialRef===e)||(await t.assertOwned(),await this.references.remove(e))})})}async project(e,t,i,r){return await this.exclusive(async()=>{const s=await this.store.load();this.rememberDesired(s);const n=s.servers.find(t=>t.name===e);if(t?.aborted)return pending(s.revision);if(i&&!matchesProjectionAttempt(e,n,i))return blocked(s.revision);const a=i?await this.findAttemptActive(s,n,i):void 0;if((i||this.releases)&&!a){this.activeCredentialRevisions.delete(e),this.liveReleaseKeys.delete(e),await this.projection.unset(e);const t=pending(s.revision);return this.projections.set(e,t),t}if(i&&!r&&n?.credentialRef&&this.credentials?.currentAuthority()?.runtimeEpoch!==i.runtimeEpoch)return this.projections.set(e,blocked(s.revision)),blocked(s.revision);if(!n){this.activeCredentialRevisions.delete(e),await this.projection.unset(e);const t=synced(s.revision);return this.projections.set(e,t),withOperationReceipt(t,s,void 0,i,a)}const o=a?releaseKey(a):void 0;if(o&&this.liveReleaseKeys.get(e)===o&&this.projection.list().some(t=>t.name===e)){const t=synced(s.revision);return this.projections.set(e,t),withOperationReceipt(t,s,n,i,a)}const c=await this.reconcileServer(n,s,t,a,i,r);return"synced"===c.state&&o&&this.liveReleaseKeys.set(e,o),withOperationReceipt(c,s,n,i,a)})}async stop(){await this.exclusive(async()=>{this.started=!1,this.unsubscribe?.(),this.unsubscribe=void 0,this.activeCredentialRevisions.clear(),this.revoked.clear(),this.projections.clear(),this.credentialServers.clear(),this.liveReleaseKeys.clear(),this.authorityEpoch=null,await this.projection.close()})}nudgeNativeProjection(){this.liveReleaseKeys.clear(),this.reconnect().catch(e=>this.logger?.warn?.(`[mcp-desired] native projection nudge failed: ${e instanceof Error?e.message:e}`))}acceptCredentialEvent(e){if("authority"===e.type){this.authorityEpoch=null;const t=++this.authorityGeneration;for(const e of this.credentialServers.keys())this.activeCredentialRevisions.delete(e),this.liveReleaseKeys.delete(e),this.projections.set(e,pending(this.desiredRevision));const i=this.projection.detachMany(this.credentialServers.keys()).then(()=>({ok:!0}),e=>({ok:!1,error:e}));return void this.exclusive(()=>this.onAuthorityEvent(e.runtimeEpoch,t,i))}this.exclusive(()=>this.onCredentialEvent(e))}async onAuthorityEvent(e,t,i){const r=await i;if(!r.ok)throw this.authorityCloseBlocked=!0,r.error;const s=await this.store.load();if(this.rememberDesired(s),t!==this.authorityGeneration||null===e||this.authorityCloseBlocked)return;const n=this.credentials?.currentAuthority();n&&n.runtimeEpoch===e&&(this.authorityEpoch=e,await this.reconcileAll(void 0,s))}async onCredentialEvent(e){const t=BigInt(e.credentialRevision),i=this.revoked.get(e.credentialRef)??-1n,r=await this.store.load();this.rememberDesired(r);const s=r.servers.filter(t=>t.credentialRef===e.credentialRef);if("revoked"!==e.type){if(!(t<=i)){this.revoked.delete(e.credentialRef);for(const e of s){if(t>(this.activeCredentialRevisions.get(e.name)??-1n)){if(this.activeCredentialRevisions.delete(e.name),this.liveReleaseKeys.delete(e.name),await this.projection.unset(e.name),!this.releases){await this.reconcileServer(e,r);continue}this.projections.set(e.name,blocked(r.revision))}}}}else{if(t<i)return;this.revoked.set(e.credentialRef,t);for(const e of s)this.activeCredentialRevisions.delete(e.name),this.liveReleaseKeys.delete(e.name),this.projections.set(e.name,blocked(r.revision)),await this.projection.unset(e.name)}}async reconcileAll(e,t){const i=t??await this.store.load();if(this.rememberDesired(i),!this.releases){const t=new Set(i.servers.map(e=>e.name));for(const e of this.projection.list())t.has(e.name)||await this.projection.unset(e.name);for(const t of i.servers)await this.reconcileServer(t,i,e);return}const r=await this.releases.listMcpActiveDesiredStates(),s=await this.releases.listMcpLiveReleases(),n=new Set(i.servers.map(e=>e.name));for(const e of this.projection.list())n.has(e.name)||await this.projection.unset(e.name);for(const t of i.servers){let n=r.find(e=>releaseMatchesDesired(e,i,t));if(!n){const e=s.find(e=>releaseMatchesDesired(e,i,t)),r=void 0===e?"pending":await this.recoverPendingServiceRelease(e,i,t);if("string"==typeof r){this.activeCredentialRevisions.delete(t.name),this.liveReleaseKeys.delete(t.name),await this.projection.unset(t.name),this.projections.set(t.name,"pending"===r?pending(i.revision):blocked(i.revision));continue}n=r}if(t.credentialRef&&this.credentials?.currentAuthority()?.runtimeEpoch!==n.runtimeEpoch){this.activeCredentialRevisions.delete(t.name),this.liveReleaseKeys.delete(t.name),await this.projection.unset(t.name),this.projections.set(t.name,blocked(i.revision));continue}const a=releaseKey(n);if(this.liveReleaseKeys.get(t.name)===a&&this.projection.list().some(e=>e.name===t.name)){this.projections.set(t.name,synced(i.revision));continue}"synced"===(await this.reconcileServer(t,i,e,n)).state&&this.liveReleaseKeys.set(t.name,releaseKey(n))}}async recoverPendingServiceRelease(e,t,i){if("service"!==e.source)return"blocked";if(!this.releases||!releaseMatchesDesired(e,t,i))return"blocked";let r;try{r=await this.releases.get(e.operationId)}catch{return"pending"}if(!r||!releaseMatchesJournalRecord(e,r))return"blocked";const s=await this.readReceiptHistory(e.operationId);if("unavailable"===s.status)return"pending";if("not_found"===s.status||"expired"===s.status)return"blocked";if(!serviceHistoryMatchesRelease(e,r,s.snapshot))return"blocked";try{const r=(await this.releases.promoteMcpDesiredStateActive({operationId:e.operationId,source:"service",effectRecordHash:e.effectRecordHash,terminalMessageId:s.snapshot.final.message_id})).mcpActiveDesiredState;return r&&releaseMatchesDesired(r,t,i)?r:"blocked"}catch{return"pending"}}async readReceiptHistory(e){const t=this.receiptHistory;if(!t)return{status:"unavailable"};const i=async()=>{try{return await t.readOperationHistory({operationId:e})}catch{return{status:"unavailable"}}},r=await i();return"unavailable"!==r.status?r:(await Promise.resolve(),await i())}async reconcileServer(e,t,i,r,s,n){try{e.credentialRef&&this.projection.list().some(t=>t.name===e.name)&&await this.projection.detach(e.name),await this.installServer(e,t,i,r,s,n);const a=synced(t.revision);return this.projections.set(e.name,a),a}catch(i){if(i instanceof McpCredentialRevokedError){this.revoked.set(e.credentialRef,this.revoked.get(e.credentialRef)??0n),this.activeCredentialRevisions.delete(e.name),await this.projection.unset(e.name);const i=blocked(t.revision);return this.projections.set(e.name,i),i}this.logger?.warn?.(`[mcp-desired] projection failed name=${e.name}: ${i instanceof Error?i.message:i}`);const r=pending(t.revision);return this.projections.set(e.name,r),r}}async installServer(e,t,i,r,s,n){if(i?.aborted)throw new Error("MCP projection attempt is no longer authoritative");if(n){if(!s||n.runtimeEpoch!==s.runtimeEpoch||r&&!releaseAuthorizesServer(r,e,t,n.runtimeEpoch,s))throw new Error("MCP operation-scoped activation is no longer authoritative");const a={generation:this.authorityGeneration,isCurrent:()=>!i?.aborted&&n.runtimeEpoch===s.runtimeEpoch},o=await this.projection.set(e.name,n.config,n.credential,a);if(!a.isCurrent())throw await this.projection.detach(e.name),new Error("MCP operation-scoped activation lost authority during OpenClaw installation");return o}if(r&&!releaseAuthorizesServer(r,e,t,this.credentials?.currentAuthority()?.runtimeEpoch,s))throw new Error("MCP live release proof no longer authorizes this server");if(!e.credentialRef){if(this.activeCredentialRevisions.delete(e.name),!e.config)throw new Error("MCP desired state has no persisted public config for this server");const t=await this.projection.set(e.name,e.config);if(i?.aborted)throw await this.projection.detach(e.name),new Error("MCP projection attempt lost authority during OpenClaw installation");return t}if(!this.credentials)throw new Error("runtime-scoped MCP credential resolver is unavailable");if(this.revoked.has(e.credentialRef))throw new McpCredentialRevokedError(e.credentialRef);const a=this.credentials.currentAuthority();if(!a||null===this.authorityEpoch||a.runtimeEpoch!==this.authorityEpoch)throw new Error("runtime-scoped MCP credential authority is pending");const o=this.authorityGeneration,c=this.authorityEpoch,d={generation:o,isCurrent:()=>o===this.authorityGeneration&&this.authorityEpoch===c&&this.credentials?.currentAuthority()?.runtimeEpoch===c};if(!d.isCurrent())throw new Error("runtime-scoped MCP credential authority changed");const h=await this.credentials.resolve({credentialRef:e.credentialRef,serverName:e.name,...i?{signal:i}:{}});if(i?.aborted||h.runtimeEpoch!==c||!d.isCurrent()||r&&!releaseAuthorizesServer(r,e,t,this.credentials?.currentAuthority()?.runtimeEpoch,s))throw new Error("runtime-scoped MCP credential authority changed during resolution");const l=BigInt(h.credentialRevision),u=this.revoked.get(e.credentialRef);if(void 0!==u&&l<=u)throw new McpCredentialRevokedError(e.credentialRef);if(!e.config)throw new Error("MCP desired state has no persisted public config for this server");const p=await this.projection.set(e.name,e.config,h.credential,d);if(i?.aborted||!d.isCurrent()||r&&!releaseAuthorizesServer(r,e,t,this.credentials?.currentAuthority()?.runtimeEpoch,s))throw await this.projection.detach(e.name),new Error("runtime-scoped MCP credential authority changed during OpenClaw installation");return this.activeCredentialRevisions.set(e.name,l),p}rememberDesired(e){this.desiredRevision=e.revision,this.credentialServers.clear();for(const t of e.servers)t.credentialRef&&this.credentialServers.set(t.name,t.credentialRef)}async recordDesiredStateCommit(e,t,i){if(!i)return;if(!this.releases)throw new Error("MCP desired state requires the shared command journal release authority");const r=e.servers.find(e=>e.name===t);await this.releases.recordMcpDesiredStateCommit({schemaVersion:1,...i,desiredStateRevision:e.revision,desiredStateHash:e.sha256,server:r?{name:r.name,definitionRef:r.definitionRef,version:r.version,definitionSha256:r.sha256,...r.credentialRef?{credentialRef:r.credentialRef}:{}}:{name:t,removed:!0}})}async findAttemptActive(e,t,i){const r=(await(this.releases?.listMcpActiveDesiredStates())??[]).find(e=>e.operationId===i.operationId&&e.method===i.method&&e.source===i.source);if(r&&releaseMatchesAttempt(r,e,t,i))return r}async collectLocalGarbage(e,t=!1){if(!this.references)return;if(!e)return void await this.store.transaction(async e=>this.collectLocalGarbage(e,t));const i=await this.references.listOwned(),r=t?await(this.references.listPendingOwned?.())??[]:[];for(const t of[...new Set([...i,...r])]){const i=(await e.load()).servers.some(e=>e.credentialRef===t);await e.assertOwned(),i?r.includes(t)&&await this.references.commit(t).catch(()=>{}):await this.references.remove(t).catch(()=>{})}}async exclusive(e){const t=this.tail.catch(()=>{}).then(e);return this.tail=t.catch(()=>{}),await t}}function pending(e){return Object.freeze({revision:e,state:"pending"})}function synced(e){return Object.freeze({revision:e,state:"synced"})}function blocked(e){return Object.freeze({revision:e,state:"blocked"})}function matchesProjectionAttempt(e,t,i){return i.expected.name===e&&(i.expected.absent?void 0===t:void 0!==t&&t.name===i.expected.name&&t.definitionRef===i.expected.definitionRef&&t.version===i.expected.version&&t.sha256===i.expected.sha256&&t.credentialRef===i.expected.credentialRef)}function withOperationReceipt(e,t,i,r,s){if(!r||!s||"synced"!==e.state)return e;const n=Object.freeze({schemaVersion:1,operationId:r.operationId,method:r.method,runtimeEpoch:r.runtimeEpoch,desiredStateRevision:t.revision,desiredStateHash:t.sha256,server:Object.freeze(i?{name:i.name,definitionRef:i.definitionRef,definitionSha256:i.sha256}:{name:r.expected.name,removed:!0})});return Object.freeze({...e,mcpReceipt:n})}function releaseMatchesDesired(e,t,i){return"mcp.set"===e.method&&e.desiredStateRevision===t.revision&&e.desiredStateHash===t.sha256&&e.server.name===i.name&&e.server.definitionRef===i.definitionRef&&e.server.version===i.version&&e.server.definitionSha256===i.sha256&&e.server.credentialRef===i.credentialRef}function releaseMatchesJournalRecord(e,t){const i=t.mcpDesiredStateCommit,r=t.mcpLiveRelease;return t.operationId===e.operationId&&t.command===e.method&&t.paramsHash===e.paramsHash&&t.runtimeEpoch===e.runtimeEpoch&&("effect_committed"===t.phase||"projection_committed"===t.phase)&&void 0!==t.messageBinding&&void 0!==i&&void 0!==r&&"service"===i.source&&sameMcpRelease(r,e)&&r.effectRecordHash===e.effectRecordHash&&sameMcpRelease(i,e)}function serviceHistoryMatchesRelease(e,t,i){const r=t.messageBinding;if(!r||!e.effectRecordHash)return!1;const s=i.request,n=s.payload,a=i.effectCommitted,o=a?.payload;if(i.operationId!==e.operationId||i.requestMessageId!==r.requestMessageId||i.requestHash!==r.requestHash||s.type!==RUNTIME_MESSAGE_TYPES.request||s.channel_id!==e.operationId||s.message_id!==r.requestMessageId||n.operationId!==e.operationId||n.requestMessageId!==r.requestMessageId||n.requestHash!==r.requestHash||n.paramsHash!==e.paramsHash||("mcp.set"===e.method?"mcp/set"!==n.method:"mcp/unset"!==n.method)||n.command.name!==e.method||n.dispatchEpoch!==e.runtimeEpoch||!a||!o||a.type!==RUNTIME_MESSAGE_TYPES.effectCommitted||a.channel_id!==e.operationId||a.in_reply_to!==r.requestMessageId||a.publisher_id!==n.instanceId||o.operationId!==e.operationId||o.requestMessageId!==r.requestMessageId||o.requestHash!==r.requestHash||o.dispatchEpoch!==e.runtimeEpoch||o.method!==n.method||o.effectRecordHash!==e.effectRecordHash||o.effectRecord.operationId!==e.operationId||o.effectRecord.method!==n.method)return!1;if(i.cancelRequested)return!1;if(!i.terminal)return!1;const c=i.final;return"final_message"===i.terminalSource&&"succeeded"===i.status&&void 0!==c&&c.type===RUNTIME_MESSAGE_TYPES.final&&c.channel_id===e.operationId&&c.in_reply_to===r.requestMessageId&&c.publisher_id===n.instanceId&&c.payload.operationId===e.operationId&&c.payload.requestMessageId===r.requestMessageId&&c.payload.requestHash===r.requestHash&&c.payload.dispatchEpoch===e.runtimeEpoch&&c.payload.method===n.method&&"succeeded"===c.payload.outcome&&c.payload.effectRecordHash===e.effectRecordHash}function sameMcpRelease(e,t){return e.source===t.source&&e.operationId===t.operationId&&e.method===t.method&&e.paramsHash===t.paramsHash&&e.runtimeEpoch===t.runtimeEpoch&&e.desiredStateRevision===t.desiredStateRevision&&e.desiredStateHash===t.desiredStateHash&&e.server.name===t.server.name&&e.server.definitionRef===t.server.definitionRef&&e.server.version===t.server.version&&e.server.definitionSha256===t.server.definitionSha256&&e.server.credentialRef===t.server.credentialRef&&e.server.removed===t.server.removed}function releaseMatchesAttempt(e,t,i,r){return!(e.operationId!==r.operationId||e.method!==r.method||e.source!==r.source||e.paramsHash!==r.paramsHash||e.runtimeEpoch!==r.runtimeEpoch||e.desiredStateRevision!==t.revision||e.desiredStateHash!==t.sha256||"service"===r.source&&!e.effectRecordHash||"acp_local"===r.source&&void 0!==e.effectRecordHash)&&(r.expected.absent?void 0===i&&e.server.name===r.expected.name&&!0===e.server.removed:void 0!==i&&releaseMatchesDesired(e,t,i)&&i.definitionRef===r.expected.definitionRef&&i.version===r.expected.version&&i.sha256===r.expected.sha256&&i.credentialRef===r.expected.credentialRef)}function releaseAuthorizesServer(e,t,i,r,s){return"mcp.set"===e.method&&(!t.credentialRef||e.runtimeEpoch===r)&&releaseMatchesDesired(e,i,t)&&(!s||releaseMatchesAttempt(e,i,t,s))}function releaseKey(e){return[e.source,e.operationId,e.paramsHash,e.runtimeEpoch,e.desiredStateRevision,e.desiredStateHash,e.effectRecordHash??"local"].join("\0")}
|
|
1
|
+
import{RUNTIME_MESSAGE_TYPES}from"../runtime/runtime-message-channel-contract.js";export class McpCredentialRevokedError extends Error{credentialRef;constructor(e){super("MCP credential is revoked"),this.credentialRef=e}}export class McpDesiredStateController{store;projection;credentials;references;releases;receiptHistory;logger;tail=Promise.resolve();unsubscribe;started=!1;revoked=new Map;activeCredentialRevisions=new Map;projections=new Map;credentialServers=new Map;liveReleaseKeys=new Map;desiredRevision="0";authorityEpoch;authorityGeneration=0;constructor(e,t,i,r,s,n,a){this.store=e,this.projection=t,this.credentials=i,this.references=r,this.releases=s,this.receiptHistory=n,this.logger=a,this.authorityEpoch=i?.currentAuthority()?.runtimeEpoch??null}setRuntimeReceiptHistoryPort(e){this.receiptHistory=e}async start(e){await this.exclusive(async()=>{this.started||(this.started=!0,this.authorityEpoch=this.credentials?.currentAuthority()?.runtimeEpoch??null,this.unsubscribe=this.credentials?.subscribe(e=>this.acceptCredentialEvent(e)));const t=await this.store.load();await this.reconcileAll(e,t),await this.collectLocalGarbage(void 0,!0)})}async reconnect(e){await this.exclusive(async()=>{await this.reconcileAll(e)})}async list(){return await this.exclusive(async()=>{const e=await this.store.load();this.rememberDesired(e);const t=new Map(this.projection.list().map(e=>[e.name,e]));return{revision:e.revision,servers:e.servers.map(i=>{const r=t.get(i.name);return Object.freeze({...i,...void 0!==r?.connected?{connected:r.connected}:{},tools:r?.tools??Object.freeze([]),projection:this.projections.get(i.name)??pending(e.revision)})})}})}async set(e,t){return await this.exclusive(async()=>await this.store.transaction(async i=>{const r=await i.set(e);return e.credentialRef&&this.references?.owns(e.credentialRef)&&await this.references.commit(e.credentialRef).catch(()=>{}),this.rememberDesired(r),this.activeCredentialRevisions.delete(e.name),this.liveReleaseKeys.delete(e.name),this.projections.set(e.name,pending(r.revision)),await this.recordDesiredStateCommit(r,e.name,t),await this.collectLocalGarbage(i),r}))}async unset(e,t){return await this.exclusive(async()=>await this.store.transaction(async i=>{const r=await i.unset(e);return this.rememberDesired(r),this.activeCredentialRevisions.delete(e),this.liveReleaseKeys.delete(e),await this.projection.unset(e),this.projections.set(e,pending(r.revision)),await this.recordDesiredStateCommit(r,e,t),await this.collectLocalGarbage(i),r}))}async isCredentialReferenced(e){return await this.exclusive(async()=>(await this.store.load()).servers.some(t=>t.credentialRef===e))}async removeLocalCredentialIfUnreferenced(e){await this.exclusive(async()=>{this.references?.owns(e)&&await this.store.transaction(async t=>{(await t.load()).servers.some(t=>t.credentialRef===e)||(await t.assertOwned(),await this.references.remove(e))})})}async project(e,t,i,r){return await this.exclusive(async()=>{const s=await this.store.load();this.rememberDesired(s);const n=s.servers.find(t=>t.name===e);if(t?.aborted)return pending(s.revision);if(i&&!matchesProjectionAttempt(e,n,i))return blocked(s.revision);const a=i?await this.findAttemptActive(s,n,i):void 0;if((i||this.releases)&&!a){this.activeCredentialRevisions.delete(e),this.liveReleaseKeys.delete(e);const t=pending(s.revision);return this.projections.set(e,t),t}if(i&&!r&&n?.credentialRef&&this.credentials?.currentAuthority()?.runtimeEpoch!==i.runtimeEpoch)return this.projections.set(e,blocked(s.revision)),blocked(s.revision);if(!n){this.activeCredentialRevisions.delete(e),await this.projection.unset(e);const t=synced(s.revision);return this.projections.set(e,t),withOperationReceipt(t,s,void 0,i,a)}const o=a?releaseKey(a):void 0;if(o&&this.liveReleaseKeys.get(e)===o&&this.projection.list().some(t=>t.name===e)){const t=synced(s.revision);return this.projections.set(e,t),withOperationReceipt(t,s,n,i,a)}const c=await this.reconcileServer(n,s,t,a,i,r);return"synced"===c.state&&o&&this.liveReleaseKeys.set(e,o),withOperationReceipt(c,s,n,i,a)})}async stop(){await this.exclusive(async()=>{this.started=!1,this.unsubscribe?.(),this.unsubscribe=void 0,this.activeCredentialRevisions.clear(),this.revoked.clear(),this.projections.clear(),this.credentialServers.clear(),this.liveReleaseKeys.clear(),this.authorityEpoch=null,await this.projection.close()})}nudgeNativeProjection(){this.liveReleaseKeys.clear(),this.reconnect().catch(e=>this.logger?.warn?.(`[mcp-desired] native projection nudge failed: ${e instanceof Error?e.message:e}`))}acceptCredentialEvent(e){if("authority"===e.type){this.authorityEpoch=null;const t=++this.authorityGeneration;for(const e of this.credentialServers.keys())this.activeCredentialRevisions.delete(e),this.liveReleaseKeys.delete(e),this.projections.set(e,pending(this.desiredRevision));return void this.exclusive(()=>this.onAuthorityEvent(e.runtimeEpoch,t))}this.exclusive(()=>this.onCredentialEvent(e))}async onAuthorityEvent(e,t){const i=await this.store.load();if(this.rememberDesired(i),t!==this.authorityGeneration||null===e)return;const r=this.credentials?.currentAuthority();r&&r.runtimeEpoch===e&&(this.authorityEpoch=e,await this.reconcileAll(void 0,i))}async onCredentialEvent(e){const t=BigInt(e.credentialRevision),i=this.revoked.get(e.credentialRef)??-1n,r=await this.store.load();this.rememberDesired(r);const s=r.servers.filter(t=>t.credentialRef===e.credentialRef);if("revoked"!==e.type){if(!(t<=i)){this.revoked.delete(e.credentialRef);for(const e of s){if(t>(this.activeCredentialRevisions.get(e.name)??-1n)){if(this.activeCredentialRevisions.delete(e.name),this.liveReleaseKeys.delete(e.name),await this.projection.unset(e.name),!this.releases){await this.reconcileServer(e,r);continue}this.projections.set(e.name,blocked(r.revision))}}}}else{if(t<i)return;this.revoked.set(e.credentialRef,t);for(const e of s)this.activeCredentialRevisions.delete(e.name),this.liveReleaseKeys.delete(e.name),this.projections.set(e.name,blocked(r.revision)),await this.projection.unset(e.name)}}async reconcileAll(e,t){const i=t??await this.store.load();if(this.rememberDesired(i),!this.releases){const t=new Set(i.servers.map(e=>e.name));for(const e of this.projection.list())t.has(e.name)||await this.projection.unset(e.name);for(const t of i.servers)await this.reconcileServer(t,i,e);return}const r=await this.releases.listMcpActiveDesiredStates(),s=await this.releases.listMcpLiveReleases(),n=new Set(i.servers.map(e=>e.name));for(const e of this.projection.list())n.has(e.name)||await this.projection.unset(e.name);for(const t of i.servers){let n=r.find(e=>releaseMatchesDesired(e,i,t));if(!n){const e=s.find(e=>releaseMatchesDesired(e,i,t)),r=void 0===e?"pending":await this.recoverPendingServiceRelease(e,i,t);if("string"==typeof r){this.activeCredentialRevisions.delete(t.name),this.liveReleaseKeys.delete(t.name),t.credentialRef||await this.projection.unset(t.name),this.projections.set(t.name,"pending"===r?pending(i.revision):blocked(i.revision));continue}n=r}if(t.credentialRef){const e=await this.projection.has(t.name),r=e?synced(i.revision):pending(i.revision);this.projections.set(t.name,r),e&&this.liveReleaseKeys.set(t.name,releaseKey(n));continue}const a=releaseKey(n);if(this.liveReleaseKeys.get(t.name)===a&&this.projection.list().some(e=>e.name===t.name)){this.projections.set(t.name,synced(i.revision));continue}"synced"===(await this.reconcileServer(t,i,e,n)).state&&this.liveReleaseKeys.set(t.name,releaseKey(n))}}async recoverPendingServiceRelease(e,t,i){if("service"!==e.source)return"blocked";if(!this.releases||!releaseMatchesDesired(e,t,i))return"blocked";let r;try{r=await this.releases.get(e.operationId)}catch{return"pending"}if(!r||!releaseMatchesJournalRecord(e,r))return"blocked";const s=await this.readReceiptHistory(e.operationId);if("unavailable"===s.status)return"pending";if("not_found"===s.status||"expired"===s.status)return"blocked";if(!serviceHistoryMatchesRelease(e,r,s.snapshot))return"blocked";try{const r=(await this.releases.promoteMcpDesiredStateActive({operationId:e.operationId,source:"service",effectRecordHash:e.effectRecordHash,terminalMessageId:s.snapshot.final.message_id})).mcpActiveDesiredState;return r&&releaseMatchesDesired(r,t,i)?r:"blocked"}catch{return"pending"}}async readReceiptHistory(e){const t=this.receiptHistory;if(!t)return{status:"unavailable"};const i=async()=>{try{return await t.readOperationHistory({operationId:e})}catch{return{status:"unavailable"}}},r=await i();return"unavailable"!==r.status?r:(await Promise.resolve(),await i())}async reconcileServer(e,t,i,r,s,n){try{await this.installServer(e,t,i,r,s,n);const a=synced(t.revision);return this.projections.set(e.name,a),a}catch(i){if(i instanceof McpCredentialRevokedError){this.revoked.set(e.credentialRef,this.revoked.get(e.credentialRef)??0n),this.activeCredentialRevisions.delete(e.name),await this.projection.unset(e.name);const i=blocked(t.revision);return this.projections.set(e.name,i),i}this.logger?.warn?.(`[mcp-desired] projection failed name=${e.name}: ${i instanceof Error?i.message:i}`);const r=pending(t.revision);return this.projections.set(e.name,r),r}}async installServer(e,t,i,r,s,n){if(i?.aborted)throw new Error("MCP projection attempt is no longer authoritative");if(n){if(!s||n.runtimeEpoch!==s.runtimeEpoch||r&&!releaseAuthorizesServer(r,e,t,n.runtimeEpoch,s))throw new Error("MCP operation-scoped activation is no longer authoritative");const a={generation:this.authorityGeneration,isCurrent:()=>!i?.aborted&&n.runtimeEpoch===s.runtimeEpoch},o=await this.projection.set(e.name,n.config,n.credential,a);if(!a.isCurrent())throw new Error("MCP operation-scoped activation lost authority during OpenClaw installation");return o}if(r&&!releaseAuthorizesServer(r,e,t,this.credentials?.currentAuthority()?.runtimeEpoch,s))throw new Error("MCP live release proof no longer authorizes this server");if(!e.credentialRef){if(this.activeCredentialRevisions.delete(e.name),!e.config)throw new Error("MCP desired state has no persisted public config for this server");const t=await this.projection.set(e.name,e.config);if(i?.aborted)throw new Error("MCP projection attempt lost authority during OpenClaw installation");return t}if(!this.credentials)throw new Error("runtime-scoped MCP credential resolver is unavailable");if(this.revoked.has(e.credentialRef))throw new McpCredentialRevokedError(e.credentialRef);const a=this.credentials.currentAuthority();if(!a||null===this.authorityEpoch||a.runtimeEpoch!==this.authorityEpoch)throw new Error("runtime-scoped MCP credential authority is pending");const o=this.authorityGeneration,c=this.authorityEpoch,d={generation:o,isCurrent:()=>o===this.authorityGeneration&&this.authorityEpoch===c&&this.credentials?.currentAuthority()?.runtimeEpoch===c};if(!d.isCurrent())throw new Error("runtime-scoped MCP credential authority changed");const h=await this.credentials.resolve({credentialRef:e.credentialRef,serverName:e.name,...i?{signal:i}:{}});if(i?.aborted||h.runtimeEpoch!==c||!d.isCurrent()||r&&!releaseAuthorizesServer(r,e,t,this.credentials?.currentAuthority()?.runtimeEpoch,s))throw new Error("runtime-scoped MCP credential authority changed during resolution");const l=BigInt(h.credentialRevision),u=this.revoked.get(e.credentialRef);if(void 0!==u&&l<=u)throw new McpCredentialRevokedError(e.credentialRef);if(!e.config)throw new Error("MCP desired state has no persisted public config for this server");const p=await this.projection.set(e.name,e.config,h.credential,d);if(i?.aborted||!d.isCurrent()||r&&!releaseAuthorizesServer(r,e,t,this.credentials?.currentAuthority()?.runtimeEpoch,s))throw new Error("runtime-scoped MCP credential authority changed during OpenClaw installation");return this.activeCredentialRevisions.set(e.name,l),p}rememberDesired(e){this.desiredRevision=e.revision,this.credentialServers.clear();for(const t of e.servers)t.credentialRef&&this.credentialServers.set(t.name,t.credentialRef)}async recordDesiredStateCommit(e,t,i){if(!i)return;if(!this.releases)throw new Error("MCP desired state requires the shared command journal release authority");const r=e.servers.find(e=>e.name===t);await this.releases.recordMcpDesiredStateCommit({schemaVersion:1,...i,desiredStateRevision:e.revision,desiredStateHash:e.sha256,server:r?{name:r.name,definitionRef:r.definitionRef,version:r.version,definitionSha256:r.sha256,...r.credentialRef?{credentialRef:r.credentialRef}:{}}:{name:t,removed:!0}})}async findAttemptActive(e,t,i){const r=(await(this.releases?.listMcpActiveDesiredStates())??[]).find(e=>e.operationId===i.operationId&&e.method===i.method&&e.source===i.source);if(r&&releaseMatchesAttempt(r,e,t,i))return r}async collectLocalGarbage(e,t=!1){if(!this.references)return;if(!e)return void await this.store.transaction(async e=>this.collectLocalGarbage(e,t));const i=await this.references.listOwned(),r=t?await(this.references.listPendingOwned?.())??[]:[];for(const t of[...new Set([...i,...r])]){const i=(await e.load()).servers.some(e=>e.credentialRef===t);await e.assertOwned(),i?r.includes(t)&&await this.references.commit(t).catch(()=>{}):await this.references.remove(t).catch(()=>{})}}async exclusive(e){const t=this.tail.catch(()=>{}).then(e);return this.tail=t.catch(()=>{}),await t}}function pending(e){return Object.freeze({revision:e,state:"pending"})}function synced(e){return Object.freeze({revision:e,state:"synced"})}function blocked(e){return Object.freeze({revision:e,state:"blocked"})}function matchesProjectionAttempt(e,t,i){return i.expected.name===e&&(i.expected.absent?void 0===t:void 0!==t&&t.name===i.expected.name&&t.definitionRef===i.expected.definitionRef&&t.version===i.expected.version&&t.sha256===i.expected.sha256&&t.credentialRef===i.expected.credentialRef)}function withOperationReceipt(e,t,i,r,s){if(!r||!s||"synced"!==e.state)return e;const n=Object.freeze({schemaVersion:1,operationId:r.operationId,method:r.method,runtimeEpoch:r.runtimeEpoch,desiredStateRevision:t.revision,desiredStateHash:t.sha256,server:Object.freeze(i?{name:i.name,definitionRef:i.definitionRef,definitionSha256:i.sha256}:{name:r.expected.name,removed:!0})});return Object.freeze({...e,mcpReceipt:n})}function releaseMatchesDesired(e,t,i){return"mcp.set"===e.method&&e.desiredStateRevision===t.revision&&e.desiredStateHash===t.sha256&&e.server.name===i.name&&e.server.definitionRef===i.definitionRef&&e.server.version===i.version&&e.server.definitionSha256===i.sha256&&e.server.credentialRef===i.credentialRef}function releaseMatchesJournalRecord(e,t){const i=t.mcpDesiredStateCommit,r=t.mcpLiveRelease;return t.operationId===e.operationId&&t.command===e.method&&t.paramsHash===e.paramsHash&&t.runtimeEpoch===e.runtimeEpoch&&("effect_committed"===t.phase||"projection_committed"===t.phase)&&void 0!==t.messageBinding&&void 0!==i&&void 0!==r&&"service"===i.source&&sameMcpRelease(r,e)&&r.effectRecordHash===e.effectRecordHash&&sameMcpRelease(i,e)}function serviceHistoryMatchesRelease(e,t,i){const r=t.messageBinding;if(!r||!e.effectRecordHash)return!1;const s=i.request,n=s.payload,a=i.effectCommitted,o=a?.payload;if(i.operationId!==e.operationId||i.requestMessageId!==r.requestMessageId||i.requestHash!==r.requestHash||s.type!==RUNTIME_MESSAGE_TYPES.request||s.channel_id!==e.operationId||s.message_id!==r.requestMessageId||n.operationId!==e.operationId||n.requestMessageId!==r.requestMessageId||n.requestHash!==r.requestHash||n.paramsHash!==e.paramsHash||("mcp.set"===e.method?"mcp/set"!==n.method:"mcp/unset"!==n.method)||n.command.name!==e.method||n.dispatchEpoch!==e.runtimeEpoch||!a||!o||a.type!==RUNTIME_MESSAGE_TYPES.effectCommitted||a.channel_id!==e.operationId||a.in_reply_to!==r.requestMessageId||a.publisher_id!==n.instanceId||o.operationId!==e.operationId||o.requestMessageId!==r.requestMessageId||o.requestHash!==r.requestHash||o.dispatchEpoch!==e.runtimeEpoch||o.method!==n.method||o.effectRecordHash!==e.effectRecordHash||o.effectRecord.operationId!==e.operationId||o.effectRecord.method!==n.method)return!1;if(i.cancelRequested)return!1;if(!i.terminal)return!1;const c=i.final;return"final_message"===i.terminalSource&&"succeeded"===i.status&&void 0!==c&&c.type===RUNTIME_MESSAGE_TYPES.final&&c.channel_id===e.operationId&&c.in_reply_to===r.requestMessageId&&c.publisher_id===n.instanceId&&c.payload.operationId===e.operationId&&c.payload.requestMessageId===r.requestMessageId&&c.payload.requestHash===r.requestHash&&c.payload.dispatchEpoch===e.runtimeEpoch&&c.payload.method===n.method&&"succeeded"===c.payload.outcome&&c.payload.effectRecordHash===e.effectRecordHash}function sameMcpRelease(e,t){return e.source===t.source&&e.operationId===t.operationId&&e.method===t.method&&e.paramsHash===t.paramsHash&&e.runtimeEpoch===t.runtimeEpoch&&e.desiredStateRevision===t.desiredStateRevision&&e.desiredStateHash===t.desiredStateHash&&e.server.name===t.server.name&&e.server.definitionRef===t.server.definitionRef&&e.server.version===t.server.version&&e.server.definitionSha256===t.server.definitionSha256&&e.server.credentialRef===t.server.credentialRef&&e.server.removed===t.server.removed}function releaseMatchesAttempt(e,t,i,r){return!(e.operationId!==r.operationId||e.method!==r.method||e.source!==r.source||e.paramsHash!==r.paramsHash||e.runtimeEpoch!==r.runtimeEpoch||e.desiredStateRevision!==t.revision||e.desiredStateHash!==t.sha256||"service"===r.source&&!e.effectRecordHash||"acp_local"===r.source&&void 0!==e.effectRecordHash)&&(r.expected.absent?void 0===i&&e.server.name===r.expected.name&&!0===e.server.removed:void 0!==i&&releaseMatchesDesired(e,t,i)&&i.definitionRef===r.expected.definitionRef&&i.version===r.expected.version&&i.sha256===r.expected.sha256&&i.credentialRef===r.expected.credentialRef)}function releaseAuthorizesServer(e,t,i,r,s){return"mcp.set"===e.method&&(!t.credentialRef||e.runtimeEpoch===r)&&releaseMatchesDesired(e,i,t)&&(!s||releaseMatchesAttempt(e,i,t,s))}function releaseKey(e){return[e.source,e.operationId,e.paramsHash,e.runtimeEpoch,e.desiredStateRevision,e.desiredStateHash,e.effectRecordHash??"local"].join("\0")}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash}from"node:crypto";import{Client}from"@modelcontextprotocol/sdk/client";import{SSEClientTransport}from"@modelcontextprotocol/sdk/client/sse.js";import{StdioClientTransport,getDefaultEnvironment}from"@modelcontextprotocol/sdk/client/stdio.js";import{StreamableHTTPClientTransport}from"@modelcontextprotocol/sdk/client/streamableHttp.js";import{normalizeMcpPublicServerConfig}from"./mcp-desired-state-store.js";import{requireSecretFreeMcpServerName}from"./mcp-public-config-security.js";const DEFAULT_TIMEOUT_MS=15e3;export const MCP_BRIDGE_ACTIVATION=Object.freeze({status:"ready",blockers:Object.freeze([])});export class InProcessMcpToolBridge{connect;servers=new Map;generation=0;constructor(e=connectMcpServer){this.connect=e}registerTools(e){e.registerTool(()=>[...this.servers.values()].flatMap(e=>e.tools),{optional:!0})}list(){return[...this.servers.values()].map(e=>e.view)}async set(e,r,t,o){const n=requireServerName(e),i=normalizeMcpPublicServerConfig(r);requireAuthority(o);const s=await this.connect(i,t);let a,c,l;try{requireAuthority(o),a=await s.listTools(),requireAuthority(o);const e=new Set,r=++this.generation;c=a.map(t=>{if(!t.name||e.has(t.name))throw new Error("MCP server returned an invalid or duplicate tool name");return e.add(t.name),bridgeTool(n,t,s,()=>{const e=this.servers.get(n);if(!e||e!==l||e.generation!==r||!isAuthorityCurrent(o))throw new Error("MCP tool authority is no longer current")})}),l={view:void 0,connection:s,tools:Object.freeze([...c]),generation:r,...o?{authority:o}:{}}}catch{throw await s.close().catch(()=>{}),new Error("MCP server tool discovery failed")}const u=Object.freeze({name:n,transport:transportKind(i),connected:!0,tools:Object.freeze(a.map(e=>e.name))});l.view=u;try{requireAuthority(o)}catch{throw await s.close().catch(()=>{}),new Error("MCP server authority changed during connection")}const p=this.servers.get(n);return this.servers.set(n,l),await(p?.connection.close().catch(()=>{})),u}async unset(e){return await this.detach(e)}detach(e){const r=requireServerName(e),t=this.servers.get(r);return t?(this.servers.delete(r),t.connection.close().then(()=>!0)):Promise.resolve(!1)}detachMany(e){const r=[];for(const t of new Set(e))r.push(this.detach(t));return Promise.allSettled(r).then(e=>{const r=e.filter(e=>"rejected"===e.status);if(r.length)throw new AggregateError(r.map(e=>e.reason),"one or more MCP transports failed to close")})}async close(){await this.detachMany([...this.servers.keys()])}}export async function connectMcpServer(e,r){return await connectMcpTransport(createTransport(e,r),e.connectionTimeoutMs)}export async function connectMcpTransport(e,r=15e3){const t=new Client({name:"beeos-claw-mcp-bridge",version:"0.1.0"},{capabilities:{}}),o=validTimeout(r);try{await t.connect(e,{timeout:o})}catch{throw await t.close().catch(()=>{}),new Error("MCP server connection failed")}return{listTools:async()=>(await t.listTools({},{timeout:o})).tools.map(e=>({name:e.name,...e.description?{description:e.description}:{},...isObject(e.inputSchema)?{inputSchema:e.inputSchema}:{}})),callTool:async(e,r,n)=>await t.callTool({name:e,arguments:r},void 0,{timeout:o,...n?{signal:n}:{}}),async close(){await t.close()}}}function createTransport(e,r){const t=transportKind(e=normalizeMcpPublicServerConfig(e));if("stdio"===t){if(r?.headers&&Object.keys(r.headers).length)throw new Error("MCP stdio cannot inject HTTP headers");if("string"!=typeof e.command||!e.command.trim())throw new Error("MCP stdio command is required");const t=stringArray(e.args,"MCP stdio args"),o=stringMap(r?.env,"MCP environment");return new StdioClientTransport({command:e.command,...t?{args:t}:{},env:{...getDefaultEnvironment(),...o},stderr:"pipe",..."string"==typeof e.cwd&&e.cwd?{cwd:e.cwd}:{}})}if("string"!=typeof e.url||!e.url)throw new Error("MCP server URL is required");if(r?.env&&Object.keys(r.env).length)throw new Error("MCP HTTP transport cannot inject process environment");const o=new URL(e.url);if("https:"!==o.protocol||o.username||o.password||o.search||o.hash)throw new Error("MCP server URL must be credential-free HTTPS");const n=stringMap(r?.headers,"MCP headers");return"sse"===t?new SSEClientTransport(o,{requestInit:{headers:n},eventSourceInit:{fetch:(e,r)=>fetch(e,{...r,headers:n})}}):new StreamableHTTPClientTransport(o,{requestInit:{headers:n}})}function bridgeTool(e,r,t,o){return Object.freeze({name:publicToolName(e,r.name),description:safeDescription(e,r),parameters:normalizeSchema(r.inputSchema),async execute(e,n,i){if(!isObject(n))throw new Error("MCP tool arguments must be an object");try{return o(),await t.callTool(r.name,n,i)}catch{throw new Error("MCP tool invocation failed")}}})}function isAuthorityCurrent(e){return!e||e.isCurrent()}function requireAuthority(e){if(!isAuthorityCurrent(e))throw new Error("MCP bridge authority is no longer current")}function publicToolName(e,r){return`${`beeos_mcp_${identifier(e)}_${identifier(r)}`.slice(0,54).replace(/_+$/g,"")}_${createHash("sha256").update(`${e}\0${r}`).digest("hex").slice(0,8)}`}function identifier(e){return e.toLowerCase().replace(/[^a-z0-9_]+/g,"_").replace(/^_+|_+$/g,"")||"tool"}function safeDescription(e,r){return`MCP ${e}: ${r.description||r.name}`.replace(/[\u0000-\u001f\u007f]/g," ").slice(0,512)}function normalizeSchema(e){return!e||void 0!==e.type&&"object"!==e.type?{type:"object",properties:{},additionalProperties:!0}:void 0===e.type?{...e,type:"object"}:e}function transportKind(e){return"sse"===e.transport||"streamable-http"===e.transport||"stdio"===e.transport?e.transport:e.command?"stdio":"streamable-http"}function requireServerName(e){return requireSecretFreeMcpServerName(e)}function stringArray(e,r){if(void 0!==e){if(!Array.isArray(e)||e.some(e=>"string"!=typeof e))throw new Error(`${r} invalid`);return[...e]}}function stringMap(e,r){if(void 0===e)return{};if(!isObject(e)||Object.values(e).some(e=>"string"!=typeof e))throw new Error(`${r} invalid`);return{...e}}function validTimeout(e){return"number"==typeof e&&Number.isSafeInteger(e)&&e>0&&e<=12e4?e:15e3}function isObject(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}
|
|
1
|
+
import{createHash}from"node:crypto";import{Client}from"@modelcontextprotocol/sdk/client";import{SSEClientTransport}from"@modelcontextprotocol/sdk/client/sse.js";import{StdioClientTransport,getDefaultEnvironment}from"@modelcontextprotocol/sdk/client/stdio.js";import{StreamableHTTPClientTransport}from"@modelcontextprotocol/sdk/client/streamableHttp.js";import{normalizeMcpPublicServerConfig}from"./mcp-desired-state-store.js";import{requireSecretFreeMcpServerName}from"./mcp-public-config-security.js";const DEFAULT_TIMEOUT_MS=15e3;export const MCP_BRIDGE_ACTIVATION=Object.freeze({status:"ready",blockers:Object.freeze([])});export class InProcessMcpToolBridge{connect;servers=new Map;generation=0;constructor(e=connectMcpServer){this.connect=e}registerTools(e){e.registerTool(()=>[...this.servers.values()].flatMap(e=>e.tools),{optional:!0})}list(){return[...this.servers.values()].map(e=>e.view)}async has(e){return this.servers.has(requireServerName(e))}async set(e,r,t,o){const n=requireServerName(e),i=normalizeMcpPublicServerConfig(r);requireAuthority(o);const s=await this.connect(i,t);let a,c,l;try{requireAuthority(o),a=await s.listTools(),requireAuthority(o);const e=new Set,r=++this.generation;c=a.map(t=>{if(!t.name||e.has(t.name))throw new Error("MCP server returned an invalid or duplicate tool name");return e.add(t.name),bridgeTool(n,t,s,()=>{const e=this.servers.get(n);if(!e||e!==l||e.generation!==r||!isAuthorityCurrent(o))throw new Error("MCP tool authority is no longer current")})}),l={view:void 0,connection:s,tools:Object.freeze([...c]),generation:r,...o?{authority:o}:{}}}catch{throw await s.close().catch(()=>{}),new Error("MCP server tool discovery failed")}const u=Object.freeze({name:n,transport:transportKind(i),connected:!0,tools:Object.freeze(a.map(e=>e.name))});l.view=u;try{requireAuthority(o)}catch{throw await s.close().catch(()=>{}),new Error("MCP server authority changed during connection")}const p=this.servers.get(n);return this.servers.set(n,l),await(p?.connection.close().catch(()=>{})),u}async unset(e){return await this.detach(e)}detach(e){const r=requireServerName(e),t=this.servers.get(r);return t?(this.servers.delete(r),t.connection.close().then(()=>!0)):Promise.resolve(!1)}detachMany(e){const r=[];for(const t of new Set(e))r.push(this.detach(t));return Promise.allSettled(r).then(e=>{const r=e.filter(e=>"rejected"===e.status);if(r.length)throw new AggregateError(r.map(e=>e.reason),"one or more MCP transports failed to close")})}async close(){await this.detachMany([...this.servers.keys()])}}export async function connectMcpServer(e,r){return await connectMcpTransport(createTransport(e,r),e.connectionTimeoutMs)}export async function connectMcpTransport(e,r=15e3){const t=new Client({name:"beeos-claw-mcp-bridge",version:"0.1.0"},{capabilities:{}}),o=validTimeout(r);try{await t.connect(e,{timeout:o})}catch{throw await t.close().catch(()=>{}),new Error("MCP server connection failed")}return{listTools:async()=>(await t.listTools({},{timeout:o})).tools.map(e=>({name:e.name,...e.description?{description:e.description}:{},...isObject(e.inputSchema)?{inputSchema:e.inputSchema}:{}})),callTool:async(e,r,n)=>await t.callTool({name:e,arguments:r},void 0,{timeout:o,...n?{signal:n}:{}}),async close(){await t.close()}}}function createTransport(e,r){const t=transportKind(e=normalizeMcpPublicServerConfig(e));if("stdio"===t){if(r?.headers&&Object.keys(r.headers).length)throw new Error("MCP stdio cannot inject HTTP headers");if("string"!=typeof e.command||!e.command.trim())throw new Error("MCP stdio command is required");const t=stringArray(e.args,"MCP stdio args"),o=stringMap(r?.env,"MCP environment");return new StdioClientTransport({command:e.command,...t?{args:t}:{},env:{...getDefaultEnvironment(),...o},stderr:"pipe",..."string"==typeof e.cwd&&e.cwd?{cwd:e.cwd}:{}})}if("string"!=typeof e.url||!e.url)throw new Error("MCP server URL is required");if(r?.env&&Object.keys(r.env).length)throw new Error("MCP HTTP transport cannot inject process environment");const o=new URL(e.url);if("https:"!==o.protocol||o.username||o.password||o.search||o.hash)throw new Error("MCP server URL must be credential-free HTTPS");const n=stringMap(r?.headers,"MCP headers");return"sse"===t?new SSEClientTransport(o,{requestInit:{headers:n},eventSourceInit:{fetch:(e,r)=>fetch(e,{...r,headers:n})}}):new StreamableHTTPClientTransport(o,{requestInit:{headers:n}})}function bridgeTool(e,r,t,o){return Object.freeze({name:publicToolName(e,r.name),description:safeDescription(e,r),parameters:normalizeSchema(r.inputSchema),async execute(e,n,i){if(!isObject(n))throw new Error("MCP tool arguments must be an object");try{return o(),await t.callTool(r.name,n,i)}catch{throw new Error("MCP tool invocation failed")}}})}function isAuthorityCurrent(e){return!e||e.isCurrent()}function requireAuthority(e){if(!isAuthorityCurrent(e))throw new Error("MCP bridge authority is no longer current")}function publicToolName(e,r){return`${`beeos_mcp_${identifier(e)}_${identifier(r)}`.slice(0,54).replace(/_+$/g,"")}_${createHash("sha256").update(`${e}\0${r}`).digest("hex").slice(0,8)}`}function identifier(e){return e.toLowerCase().replace(/[^a-z0-9_]+/g,"_").replace(/^_+|_+$/g,"")||"tool"}function safeDescription(e,r){return`MCP ${e}: ${r.description||r.name}`.replace(/[\u0000-\u001f\u007f]/g," ").slice(0,512)}function normalizeSchema(e){return!e||void 0!==e.type&&"object"!==e.type?{type:"object",properties:{},additionalProperties:!0}:void 0===e.type?{...e,type:"object"}:e}function transportKind(e){return"sse"===e.transport||"streamable-http"===e.transport||"stdio"===e.transport?e.transport:e.command?"stdio":"streamable-http"}function requireServerName(e){return requireSecretFreeMcpServerName(e)}function stringArray(e,r){if(void 0!==e){if(!Array.isArray(e)||e.some(e=>"string"!=typeof e))throw new Error(`${r} invalid`);return[...e]}}function stringMap(e,r){if(void 0===e)return{};if(!isObject(e)||Object.values(e).some(e=>"string"!=typeof e))throw new Error(`${r} invalid`);return{...e}}function validTimeout(e){return"number"==typeof e&&Number.isSafeInteger(e)&&e>0&&e<=12e4?e:15e3}function isObject(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{listMcpServers,setMcpServer,unsetMcpServer}from"../openclaw-cli.js";export class OpenClawNativeMcpProjection{installed=new Map;cli;constructor(t={}){this.cli={list:t.cli?.list??listMcpServers,set:t.cli?.set??setMcpServer,unset:t.cli?.unset??unsetMcpServer}}list(){return[...this.installed.values()]}async set(t,e,r,n){
|
|
1
|
+
import{listMcpServers,setMcpServer,unsetMcpServer}from"../openclaw-cli.js";export class OpenClawNativeMcpProjection{installed=new Map;cli;constructor(t={}){this.cli={list:t.cli?.list??listMcpServers,set:t.cli?.set??setMcpServer,unset:t.cli?.unset??unsetMcpServer}}list(){return[...this.installed.values()]}async has(t){const e=(await this.cli.list())[t];return e?(this.installed.set(t,nativeView(t,e)),!0):(this.installed.delete(t),!1)}async set(t,e,r,n){requireCurrent(n),await this.cli.set(t,renderOpenClawMcpServer(e,r));const s=nativeView(t,e);if(this.installed.set(t,s),!isCurrent(n))throw new Error("MCP projection authority changed during OpenClaw installation");return s}async unset(t){return await this.detach(t)}async detach(t){const e=await this.removeIfPresent(t);return this.installed.delete(t),e}async detachMany(t){const e=[];for(const r of new Set(t))try{await this.detach(r)}catch(t){e.push(t)}if(e.length)throw new AggregateError(e,"one or more native MCP entries failed to uninstall")}async close(){this.installed.clear()}async removeIfPresent(t){const e=await this.cli.list();return!!Object.prototype.hasOwnProperty.call(e,t)&&(await this.cli.unset(t),!0)}}export function renderOpenClawMcpServer(t,e){const r=e?.headers&&Object.keys(e.headers).length?{...e.headers}:void 0,n=e?.env&&Object.keys(e.env).length?{...e.env}:void 0;return{..."stdio"!==t.transport?{transport:t.transport}:{},..."string"==typeof t.url?{url:t.url}:{},..."string"==typeof t.command?{command:t.command}:{},...Array.isArray(t.args)?{args:[...t.args]}:{},..."string"==typeof t.cwd?{cwd:t.cwd}:{},..."number"==typeof t.connectionTimeoutMs?{connectionTimeoutMs:t.connectionTimeoutMs}:{},...r?{headers:r}:{},...n?{env:n}:{}}}function transportKind(t){return"sse"===t.transport||"streamable-http"===t.transport||"stdio"===t.transport?t.transport:t.command?"stdio":"streamable-http"}function nativeView(t,e){return Object.freeze({name:t,transport:transportKind(e),tools:Object.freeze([])})}function isCurrent(t){return!t||t.isCurrent()}function requireCurrent(t){if(!isCurrent(t))throw new Error("MCP projection authority is no longer current")}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beeos-claw",
|
|
3
|
-
"version": "0.6.49-staging.
|
|
3
|
+
"version": "0.6.49-staging.3",
|
|
4
4
|
"description": "OpenClaw plugin that bridges the BeeOS platform with the local OpenClaw Gateway, providing file transfer, ACP bridge, and observability.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "BeeOS <dev@beeos.ai>",
|
|
@@ -87,5 +87,5 @@
|
|
|
87
87
|
"fast-uri": ">=3.1.4",
|
|
88
88
|
"brace-expansion": ">=5.0.9"
|
|
89
89
|
},
|
|
90
|
-
"gitHead": "
|
|
90
|
+
"gitHead": "e7f69dc388d10c8a835831d3c9628605fd571455"
|
|
91
91
|
}
|