@ours.network/mcp 1.2.0-nightly.1 → 1.2.0-nightly.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.
- package/dist/cli.js +1 -1
- package/dist/network-integration.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import{spawn as Gt}from"node:child_process";import{readFileSync as Vt,realpathSync as V}from"node:fs";import{fileURLToPath as Wt}from"node:url";import{attachOursClient as B,resolveDaemonConfig as Yt}from"@ours.network/sdk";import{randomUUID as z}from"node:crypto";import{chmod as X,mkdir as Q,readFile as Z,rename as tt,unlink as et,writeFile as nt}from"node:fs/promises";import{homedir as rt}from"node:os";import{dirname as it,resolve as u}from"node:path";var R=1,ot="OURS_MCP_CONFIG",T=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,st=()=>({version:1,daemons:{},instances:{}});function v(t=process.env){let e=(t[ot]??"").trim();return u(e||u(rt(),".ours-mcp","config.json"))}function x(t,e,n,r){if(!t||typeof t!="object"||Array.isArray(t))throw new Error(`Invalid ours-mcp application identity config at ${e}: "${n}s" must be an object.`);let i={};for(let[o,s]of Object.entries(t)){if(!s||typeof s!="object"||Array.isArray(s))throw new Error(`Invalid ours-mcp application identity config at ${e}: ${n} ${JSON.stringify(o)} must be an object.`);let a=s;if(!Array.isArray(a.identities)||a.identities.some(l=>typeof l!="string"||l.length===0))throw new Error(`Invalid ours-mcp application identity config at ${e}: ${n} ${JSON.stringify(o)} has an invalid identity list.`);i[r(o)]={identities:[...new Set(a.identities)].sort()}}return i}function at(t,e){let n;try{n=JSON.parse(t)}catch(s){throw new Error(`Invalid ours-mcp application identity config at ${e}: ${String(s)}`)}if(!n||typeof n!="object"||Array.isArray(n))throw new Error(`Invalid ours-mcp application identity config at ${e}: expected an object.`);let r=n;if(r.version!==R)throw new Error(`Unsupported ours-mcp application identity config version at ${e}: expected ${R}, found ${String(r.version)}.`);let i=x(r.daemons,e,"daemon",u),o=r.instances===void 0?{}:x(r.instances,e,"instance",s=>{if(!T.test(s))throw new Error(`Invalid ours-mcp application identity config at ${e}: instance ${JSON.stringify(s)} must be a lowercase UUID.`);return s});return{version:1,daemons:i,instances:o}}async function y(t){try{return at(await Z(t,"utf8"),t)}catch(e){if(e.code==="ENOENT")return st();throw e}}async function _(t,e){let n=it(t);await Q(n,{recursive:!0,mode:448});let r=`${t}.${process.pid}.${z()}.tmp`;try{await nt(r,`${JSON.stringify(e,null,2)}
|
|
3
3
|
`,{mode:384,flag:"wx"}),await tt(r,t),await X(t,384)}catch(i){try{await et(r)}catch{}throw i}}var f=class{stateDir;instanceId;path;constructor(e,n={}){if(typeof e=="string")this.stateDir=u(e),this.instanceId=null;else{if(!e||!T.test(e.instanceId))throw new Error("Application identity instanceId must be a lowercase UUID.");this.stateDir=null,this.instanceId=e.instanceId}this.path=u(n.path??v(n.env))}rows(e){return this.instanceId===null?e.daemons:e.instances}key(){return this.instanceId??this.stateDir}async list(){let e=await y(this.path);return[...this.rows(e)[this.key()]?.identities??[]]}async has(e){return(await this.list()).includes(e)}async add(e){if(!e)throw new Error("Cannot add an empty identity name to ours-mcp.");let n=await y(this.path),r=this.rows(n),i=this.key(),o=r[i]?.identities??[];o.includes(e)||(r[i]={identities:[...o,e].sort()},await _(this.path,n))}async remove(e){let n=await y(this.path),r=this.rows(n),i=this.key(),o=r[i];if(!o?.identities.includes(e))return;let s=o.identities.filter(a=>a!==e);s.length===0?delete r[i]:r[i]={identities:s},await _(this.path,n)}};async function $(t,e){let n=new Set(await t.list());return e.filter(r=>n.has(r.name))}import{existsSync as ct,lstatSync as U,readFileSync as dt}from"node:fs";import{homedir as A}from"node:os";import{resolve as m}from"node:path";var I=["endpoint","expectedInstanceId","credentialPath"],lt=["port","stateDir","apiToken","apiVisibility"],pt=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,ut=["OURS_API_TOKEN","OURS_PORT","OURS_STATE_DIR","OURS_DAEMON_ID"];function d(t){return new Error(`Invalid external host profile: ${t}`)}function E(t){if(!t||typeof t!="object"||Array.isArray(t))throw d("expected a complete host profile object.");let e=t;if(I.filter(l=>Object.hasOwn(e,l)).length!==I.length)throw d("expected a complete host profile tuple: endpoint, expectedInstanceId, credentialPath.");let r=lt.filter(l=>Object.hasOwn(e,l));if(r.length)throw d(`legacy selection keys cannot be mixed with a host profile (${r.join(", ")}).`);let i=e.endpoint,o=e.expectedInstanceId,s=e.credentialPath;if(typeof i!="string"||i.trim()!==i||i==="")throw d("endpoint must be a non-empty HTTP origin.");if(typeof o!="string"||!pt.test(o))throw d("expectedInstanceId must be a lowercase UUID.");if(typeof s!="string"||s===""||!s.startsWith("/")||m(s)!==s)throw d("credentialPath must be a normalized absolute path.");let a;try{a=new URL(i)}catch{throw d("endpoint must be an HTTP origin.")}if(a.protocol!=="http:"||a.username||a.password||a.pathname!=="/"||a.search||a.hash)throw d("endpoint must be an HTTP origin without credentials, path, query, or fragment.");return{endpoint:a.origin,expectedInstanceId:o,credentialPath:s}}function ft(t){let e;try{e=dt(t,"utf8")}catch{throw new Error(`Cannot read host profile ${JSON.stringify(t)}.`)}let n;try{n=JSON.parse(e)}catch{throw d(`config ${JSON.stringify(t)} is not valid JSON.`)}if(!n||typeof n!="object"||Array.isArray(n))throw d(`config ${JSON.stringify(t)} must be an object.`);return n}function mt(t){let e;try{e=U(t)}catch{throw new Error(`Cannot read host profile ${JSON.stringify(t)}.`)}if(!e.isFile())throw d(`config ${JSON.stringify(t)} must be a regular file.`);let n=process.getuid?.();if(n===void 0||e.uid!==n)throw d(`config ${JSON.stringify(t)} must be owned by the current user.`);if(e.mode&63)throw d(`config ${JSON.stringify(t)} must have private permissions.`)}function wt(t){let e=ft(t);return I.filter(r=>Object.hasOwn(e,r)).length===0?null:(mt(t),E(e))}function gt(t,e){return(t[e]??"").trim()!==""}function ht(t=process.env){let e=(t.OURS_CONFIG??"").trim(),n=m(t.HOME||A(),".ours-client","profile.json"),r=e;if(!r)try{U(n),r=n}catch(s){if(s.code!=="ENOENT")throw new Error(`Cannot read host profile ${JSON.stringify(n)}.`);if(r=m(A(),".ours","config.json"),!ct(r))return null}let i=wt(r);if(i===null){if(m(r)===n)throw d("managed client config must contain a complete host profile tuple.");return null}let o=ut.filter(s=>gt(t,s));if(o.length)throw d(`${o.join(", ")} conflicts with host-profile mode.`);return{profile:i,configPath:r}}function S(t=process.env){return ht(t)?.profile??null}import{createHash as bt,randomUUID as Ct}from"node:crypto";import{chmod as j,mkdir as Rt,readFile as xt,rename as _t,unlink as Tt,writeFile as $t}from"node:fs/promises";import{dirname as H,join as At,resolve as Ut}from"node:path";import{attachOursClient as kt}from"@ours.network/sdk";import{createHash as yt}from"node:crypto";import{openSync as vt,closeSync as It,lstatSync as N,mkdirSync as Et,chmodSync as St,constants as w}from"node:fs";import{join as k}from"node:path";import{createRequire as D}from"node:module";var Nt=5e3,p=null,L=!1;function Ot(){if(p)return p;if(L)throw new Error("No SQLite implementation available for native session guard");L=!0;try{return p=D(import.meta.url)("node:sqlite").DatabaseSync,p}catch{try{let t=D(import.meta.url)("better-sqlite3");return p=t.default??t,p}catch{throw new Error("Native session guard requires node:sqlite (Node >=22.13) or better-sqlite3")}}}function Pt(t){try{let n=vt(t,w.O_RDWR|w.O_CREAT|w.O_EXCL|w.O_NOFOLLOW,384);It(n)}catch(n){if(n.code!=="EEXIST")throw n}let e=N(t);if(!e.isFile()||e.uid!==process.getuid()||e.mode&63||e.nlink!==1)throw new Error("Native session guard file must be an owner-only regular file")}function J(t){let e=process.getuid(),n=k(`/tmp/ours-${e}`,"session-guards");Et(n,{recursive:!0,mode:448});try{St(n,448)}catch{}let r=yt("sha256").update(t).digest("hex"),i=k(n,`${r}.guard`);Pt(i);let o=N(i),s=Ot(),a=new s(i);try{let l=N(i);if(o.dev!==l.dev||o.ino!==l.ino||o.mode!==l.mode||o.uid!==l.uid)throw new Error("Native session guard file changed during acquisition");return a.exec(`PRAGMA busy_timeout=${Nt}; BEGIN EXCLUSIVE`),()=>{try{a.close()}catch{}}}catch(l){try{a.close()}catch{}throw l}}var P=1,F=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,O=new Map,g=new Map;function Dt(t){if(typeof t!="string"||t.length===0||t.length>4096||t.trim()!==t||t.includes("\0"))throw new Error("Native session metadata is missing or invalid for this host-profile tool call.");return t}function Lt(t,e,n){if(!t||!t.startsWith("/")||Ut(t)!==t)throw new Error("Native session host record root must be a normalized absolute path.");if(!F.test(e))throw new Error("Native session daemon instance must be a lowercase UUID.");let r=bt("sha256").update(Dt(n),"utf8").digest("hex");return At(t,"sessions",e,`${r}.json`)}function Jt(t,e){let n;try{n=JSON.parse(t)}catch(o){throw new Error(`Invalid native session record at ${e}: ${String(o)}`)}if(!n||typeof n!="object"||Array.isArray(n))throw new Error(`Invalid native session record at ${e}: expected an object.`);let r=n;if(Object.keys(r).sort().join(",")!=="ownerInstanceId,state,version"||r.version!==P||typeof r.ownerInstanceId!="string"||!F.test(r.ownerInstanceId)||!["active","pending","ended"].includes(String(r.state)))throw new Error(`Invalid native session record at ${e}.`);return r}async function M(t){try{return Jt(await xt(t,"utf8"),t)}catch(e){if(e.code==="ENOENT")return null;throw e}}async function q(t,e){let n=H(t);await Rt(n,{recursive:!0,mode:448}),await j(n,448);let r=`${t}.${process.pid}.${Ct()}.tmp`;try{await $t(r,`${JSON.stringify(e,null,2)}
|
|
4
|
-
`,{mode:384,flag:"wx"}),await _t(r,t),await j(t,384)}catch(i){try{await Tt(r)}catch{}throw i}}async function jt(t,e){let n=g.get(t)??Promise.resolve(),r,i=new Promise(a=>{r=a}),o=n.catch(()=>{}).then(()=>i);g.set(t,o),await n.catch(()=>{});let s;try{return s=J(t),await e()}finally{try{s?.()}finally{r(),g.get(t)===o&&g.delete(t)}}}function Ht(t,e){return kt({endpoint:t.endpoint,expectedInstanceId:t.expectedInstanceId,credentialPath:t.credentialPath,sessionMode:"external",leaseToken:e,env:{}})}async function Ft(t,e,n){let r=O.get(t),i=r?.ownerInstanceId===n?await r.promise:await Ht(e,n);try{await i.releaseLease()}finally{r?.ownerInstanceId!==n&&await i.close()}}async function Mt(t,e,n){await Ft(t,e,n);let r=await M(t);r?.state==="pending"&&r.ownerInstanceId===n&&await q(t,{version:P,ownerInstanceId:n,state:"ended"});let i=O.get(t);i?.ownerInstanceId===n&&(O.delete(t),i.promise.then(o=>o.close()).catch(()=>{}))}async function G(t,e,n=process.env){return qt(t,e,H(v(n)))}async function qt(t,e,n){let r=E(t),i=Lt(n,r.expectedInstanceId,e);await jt(i,async()=>{let o=await M(i);o===null||o.state==="ended"||(o.state==="active"&&(o={version:P,ownerInstanceId:o.ownerInstanceId,state:"pending"},await q(i,o)),await Mt(i,r,o.ownerInstanceId))})}var b="1.2.0-nightly.
|
|
4
|
+
`,{mode:384,flag:"wx"}),await _t(r,t),await j(t,384)}catch(i){try{await Tt(r)}catch{}throw i}}async function jt(t,e){let n=g.get(t)??Promise.resolve(),r,i=new Promise(a=>{r=a}),o=n.catch(()=>{}).then(()=>i);g.set(t,o),await n.catch(()=>{});let s;try{return s=J(t),await e()}finally{try{s?.()}finally{r(),g.get(t)===o&&g.delete(t)}}}function Ht(t,e){return kt({endpoint:t.endpoint,expectedInstanceId:t.expectedInstanceId,credentialPath:t.credentialPath,sessionMode:"external",leaseToken:e,env:{}})}async function Ft(t,e,n){let r=O.get(t),i=r?.ownerInstanceId===n?await r.promise:await Ht(e,n);try{await i.releaseLease()}finally{r?.ownerInstanceId!==n&&await i.close()}}async function Mt(t,e,n){await Ft(t,e,n);let r=await M(t);r?.state==="pending"&&r.ownerInstanceId===n&&await q(t,{version:P,ownerInstanceId:n,state:"ended"});let i=O.get(t);i?.ownerInstanceId===n&&(O.delete(t),i.promise.then(o=>o.close()).catch(()=>{}))}async function G(t,e,n=process.env){return qt(t,e,H(v(n)))}async function qt(t,e,n){let r=E(t),i=Lt(n,r.expectedInstanceId,e);await jt(i,async()=>{let o=await M(i);o===null||o.state==="ended"||(o.state==="active"&&(o={version:P,ownerInstanceId:o.ownerInstanceId,state:"pending"},await q(i,o)),await Mt(i,r,o.ownerInstanceId))})}var b="1.2.0-nightly.3",c=t=>{process.stdout.write(`${t}
|
|
5
5
|
`)},K=t=>{process.stderr.write(`${t}
|
|
6
6
|
`)},W=t=>Number.isInteger(t)&&t>1,Y=Number(process.env.OURS_CLIENT_PID),h=W(Y)?Y:W(process.ppid)?process.ppid:process.pid,C=(process.env.CLAUDE_CODE_SESSION_ID??"").trim()||`ours-mcp:${h}`,Bt=(process.env.OURS_BIND_IDENTITY??"").trim()||void 0,Kt=new Set(["start","stop","restart","serve","run","status","install-service","uninstall-service"]);function zt(t){if(t.some(e=>e==="--application"||e.startsWith("--application=")))throw new Error("`--application` is no longer supported. ours-mcp connects to one coherently selected shared daemon; use OURS_CONFIG or a matching OURS_PORT + OURS_STATE_DIR selection.")}async function Xt(t){let n=(process.env.OURS_CLI??"").trim()||"ours";await new Promise((r,i)=>{let o=Gt(n,t,{stdio:"inherit",env:process.env});o.once("error",s=>{if(s.code==="ENOENT"){i(new Error(`Cannot find the ${JSON.stringify(n)} CLI. Install @ours.network/cli@2.7.2, put \`ours\` on PATH, or set OURS_CLI to its executable path.`));return}i(s)}),o.once("exit",(s,a)=>{if(a){process.kill(process.pid,a);return}process.exitCode=s??1,r()})})}async function Qt(){let t=new URL("./connector.js",import.meta.url),{runConnector:e}=await import(t.href);await e({leaseToken:C,clientPid:h,version:b,bindIdentity:Bt})}async function Zt(){let t=S(process.env);if(t!==null){let n;try{n=JSON.parse(Vt(0,"utf8"))}catch{throw new Error("SessionEnd requires valid hook JSON on stdin.")}let r=n?.session_id;if(typeof r!="string"||!r.trim())throw new Error("SessionEnd hook input must contain a non-empty session_id.");await G(t,r,process.env);return}await(await B({leaseToken:C,clientPid:h})).releaseLease()}function te(t,e){return JSON.stringify({identity:t,...e})}async function ee(t,e,n){for await(let r of t.watchNotifications(e,{kinds:["inbound"],signal:n}))c(te(e,r))}async function ne(t){if(t.length>1||t[0]?.startsWith("-"))throw new Error("Usage: ours-mcp watch [identity]");if(S(process.env)!==null)throw new Error("External owner context is required.");let e=Yt(),n=new f(e.expectStateDir);await n.list();let r=await B({leaseToken:C,clientPid:h}),i=t[0]?.trim(),o;i?o=[i]:o=(await $(n,await r.identities())).map(a=>a.name);let s=new AbortController;process.once("SIGINT",()=>s.abort()),process.once("SIGTERM",()=>s.abort()),K(`ours-mcp watch: ${o.length===0?"no application identities":`watching ${o.join(", ")}`}`),await Promise.all(o.map(a=>ee(r,a,s.signal)))}function re(){c(`ours-mcp ${b} \u2014 MCP adapter for the shared ours daemon`),c(""),c("Usage: ours-mcp <command> [options]"),c(" proxy run the stdio MCP server (never starts a daemon)"),c(" session-end release this harness session and clean up its temporary identities"),c(" watch [identity] stream inbound JSON Lines; without a name, only ours-mcp identities"),c(" start|stop|restart|serve|status"),c(" install-service|uninstall-service"),c(" compatibility aliases for `ours daemon <command>`"),c(" version print the ours-mcp package version"),c(""),c("Daemon configuration and identity CLI operations moved to @ours.network/cli@2.7.2:"),c(" ours config setup"),c(" ours identity --help")}async function ie(){let t=process.argv.slice(2);zt(t);let e=t.shift()??"help";if(Kt.has(e)){await Xt(["daemon",e==="run"?"serve":e,...t]);return}switch(e){case"proxy":if(t.length)throw new Error("Usage: ours-mcp proxy");await Qt();return;case"session-end":if(t.length)throw new Error("Usage: ours-mcp session-end");await Zt();return;case"watch":await ne(t);return;case"setup":throw new Error("ours-mcp no longer owns daemon configuration. Run `ours config setup` instead.");case"create-root":case"define-local-identity-file":case"voice-setup":case"voice-status":case"stt-setup":case"stt-status":throw new Error(`ours-mcp ${e} moved to the \`ours\` CLI. Run \`ours --help\` for the typed replacement.`);case"version":case"--version":case"-v":c(b);return;case"help":case"--help":case"-h":re();return;default:throw new Error(`Unknown command ${JSON.stringify(e)}. Run \`ours-mcp --help\`.`)}}function oe(){if(!process.argv[1])return!1;try{return V(process.argv[1])===V(Wt(import.meta.url))}catch{return!1}}var se=oe();se&&ie().catch(t=>{K(`ours-mcp error: ${t instanceof Error?t.message:String(t)}`),process.exitCode=1});
|
|
@@ -78,5 +78,5 @@ Bio: ${a.bio}`:"",d=a.persona?`
|
|
|
78
78
|
Persona: ${a.persona}`:"";return L(`Bound to "${a.name}" (${a.cid})${c}.${u}${l}${d}`)}catch(a){if(a instanceof Vp)return L(a.message);throw a}}),t.tool("remove_identity","Permanently delete a persisted identity \u2014 its packet and all on-disk state. This cannot be undone.",{name:k.string().min(1).describe("Name of the identity to delete.")},async({name:o},s)=>{let i=await e(s);try{let a=await mb(r,o,()=>i.removeIdentity({name:o}));return a.kind==="temporary"&&a.close?L(`Removed temporary identity "${a.name}" and its state.${UR(a.close)}`):L(`Removed identity "${a.name}" and its state.`)}catch(a){if(a instanceof Vp&&a.code==="NO_SUCH_IDENTITY")return await r.remove(o),L(`Identity "${o}" was already absent; removed it from this ours-mcp application's identity list.`);if(a instanceof Vp)return L(a.message,!0);throw a}})}Dt();import{readFileSync as FR}from"node:fs";import{basename as VR,resolve as HR}from"node:path";import{errFileUnreadable as BR,OursError as Jp}from"@ours.network/sdk";function Xe(t,e,r=!1){let n="history_stored"in e&&e.history_stored===!1?`
|
|
79
79
|
|
|
80
80
|
WARNING: the item was not stored in this identity's local history. Delivery and local history are not transactional; there is no fallback or automatic retry.`:"";return{content:[{type:"text",text:`${t}${n}`}],structuredContent:{outcome:e},isError:r}}function gb(t,e,r={}){t.tool("send_message","Send an end-to-end-encrypted message to a known contact (by name or container id). If the recipient is not a contact yet, the connection is established automatically when possible: an intra-root sibling (a role under the same root) connects via its delegation cert, and an identity published in the host-local contact book connects via a registrar introduction \u2014 either way the message is delivered with the introduction, no invite needed. To reply to a specific message, pass reply_to_wire_id (from get_messages or list_history) and optionally reply_to_sentence (1-based index of the sentence you are answering). Requires a bound identity.",{contact:k.string().min(1).describe("Contact name or container id to send to."),text:k.string().min(1).describe("The message text."),reply_to_wire_id:k.string().optional().describe("wire_id (from get_messages or list_history) of the message this replies to."),reply_to_sentence:k.number().int().positive().optional().describe("Optional 1-based sentence index in the replied-to message.")},async({contact:n,text:o,reply_to_wire_id:s,reply_to_sentence:i},a)=>W(e(a),c=>c.sendMessage({contact:n,text:o,reply_to_wire_id:s,reply_to_sentence:i}),c=>{switch(c.kind){case"refused":return Xe(`Couldn't send to "${n}" (wire_id ${c.wireId}): their end-to-end encryption must be re-established after an upgrade before messages can go through. It was NOT sent and NOT downgraded to the old channel \u2014 the system re-offers the upgrade automatically; try again shortly.`,c,!0);case"migrating":return Xe(`Message queued for "${n}" (wire_id ${c.wireId}) \u2014 an encryption upgrade is completing; it will send automatically the moment the migration goes active (${c.queued} message${c.queued===1?"":"s"} queued).`,c);case"e2e":return c.notRetained?Xe(`Message sent to "${n}" over the end-to-end session (wire_id ${c.wireId}) \u2014 WARNING: the message body exceeds the redrive budget, so it is NOT retained for automatic resend. If the recipient loses its session, this message will NOT re-deliver automatically \u2014 confirm receipt or resend once the contact is confirmed back.`,c):Xe(`Message sent to "${n}" over the upgraded end-to-end session (wire_id ${c.wireId}).`,c);case"deferred":return Xe(`Message queued for "${n}" (wire_id ${c.wireId}) \u2014 the contact's encryption keys are being re-established after an upgrade (contact restore in progress); delivery is automatic once restored (${c.queued} message${c.queued===1?"":"s"} queued).`,c);case"introduced":return Xe(c.text,c);default:return Xe(`Message sent to "${n}" (wire_id ${c.wireId}).`,c)}})),t.tool("send_file","Send a file to a known contact (by name or container id). Provide EITHER `path` (the connector reads it as your OS user) OR `data_base64` + `filename` (inline bytes). Files and text are distinct messages \u2014 to caption a file, also send_message. Requires a bound identity.",{contact:k.string().min(1).describe("Contact name or container id to send to."),path:k.string().min(1).optional().describe("Filesystem path to the file to send (preferred). Read by the ours connector as YOUR OS user, then streamed to the daemon."),upload_id:k.string().min(1).optional().describe("Host-bridge staged upload identifier. Do not supply this directly."),data_base64:k.string().min(1).optional().describe("Inline file bytes, base64-encoded (alternative to path)."),filename:k.string().min(1).optional().describe("Filename to advertise (required with data_base64; defaults to basename of path)."),mime:k.string().optional().describe("MIME type (inferred from the path extension when omitted)."),reply_to_wire_id:k.string().optional().describe("wire_id (from unread or persistent-history tools) this file replies to."),reply_to_sentence:k.number().int().positive().optional().describe("Optional 1-based sentence index in the replied-to item.")},async({contact:n,path:o,upload_id:s,data_base64:i,filename:a,mime:c,reply_to_wire_id:u,reply_to_sentence:l},d)=>W(e(d),async f=>{if(+!!o+ +!!s+ +!!i!==1)throw new Jp("FILE_UNREADABLE","send_file requires exactly one of path, upload_id, or data_base64.");if(s){if(!r.networkHostFiles)throw new Jp("FILE_UNREADABLE","send_file upload_id is available only through the network host bridge.");return f.sendFile({contact:n,upload_id:s,filename:a,mime:c,reply_to_wire_id:u,reply_to_sentence:l})}if(!o)return f.sendFile({contact:n,data_base64:i,filename:a,mime:c,reply_to_wire_id:u,reply_to_sentence:l});if(r.networkHostFiles)throw new Jp("FILE_UNREADABLE","send_file path must be staged by the network host bridge.");let p=HR(o),h;try{h=FR(p)}catch(v){throw BR(String(v))}let g=await f.uploadFile(new Uint8Array(h),{filename:a??VR(p),mime:c});return f.sendFile({contact:n,upload_id:g.upload_id,filename:a,mime:c,reply_to_wire_id:u,reply_to_sentence:l})},f=>{let m=`File "${f.filename}" (${f.bytes} B${f.mime?`, ${f.mime}`:""})`;switch(f.kind){case"refused":return Xe(`Couldn't send ${m} to "${n}" (wire_id ${f.wireId}): their end-to-end encryption must be re-established after an upgrade first. It was NOT sent and NOT downgraded; the system re-offers the upgrade automatically \u2014 try again shortly.`,f,!0);case"migrating":return Xe(`${m} not sent to "${n}" yet \u2014 an encryption upgrade is completing; retry the file once the migration goes active (files aren't auto-queued like messages).`,f,!0);case"e2e":return f.notRetained?Xe(`${m} sent to "${n}" over the end-to-end session (wire_id ${f.wireId}) \u2014 WARNING: the file exceeds the 2 MiB redrive budget, so it is NOT retained for automatic resend. If the recipient loses its session (e.g. it was mid-restart), this file will NOT re-deliver automatically \u2014 confirm receipt or resend it once the contact is confirmed back.`,f):Xe(`${m} sent to "${n}" over the upgraded end-to-end session (wire_id ${f.wireId}).`,f);case"introduced":return Xe(f.text,f);default:return Xe(`${m} sent to "${n}" (wire_id ${f.wireId}).`,f)}})),t.tool("get_messages","Fetch the oldest unread messages for the bound identity and atomically mark that bounded batch read. Defaults to 50 and returns at most 200, plus the remaining unread count. Bodies remain available through persistent list_history/get_history_item. Read receipts are best-effort after the local read commit; there is no defer, outbox, fallback, or automatic retry.",{limit:k.number().int().min(1).max(200).optional().describe("Batch size from 1 to 200 (default 50).")},async({limit:n},o)=>W(e(o),s=>s.getMessages({limit:n}),s=>{let i={count:s.messages.length,messages:s.messages,command_results:s.command_results,commands_handled:s.commands_handled,remaining:s.remaining};return{content:[{type:"text",text:JSON.stringify(i,null,2)}],structuredContent:i,isError:!1}}))}Dt();function yb(t,e){t.tool("set_bio","Set the bound identity's profile bio (free text). For a role, the bio is embedded in the invites it generates. For the root identity, the refreshed profile is re-pinned into every role so future role invites carry the update.",{bio:k.string().describe("The new bio text (empty string clears it).")},async({bio:r},n)=>W(e(n),o=>o.setBio({bio:r}),o=>{let s=o.rolesRefreshed>0?` Root profile refreshed in ${o.rolesRefreshed} role(s).`:"";return L(`Updated the bio of "${o.identity}".${s}`)})),t.tool("advertise_migrate","Enable the e2e-migration capability (core.e2e.migrate) at runtime on the bound identity and proactively offer migration to every already-known eligible e2e contact. This is the staged-advertise trigger: an identity booted WITHOUT the migrate cap (so it forms a plain e2e session first) calls this to start the SAME migrations a default-cap boot would \u2014 closing the already-e2e-pair gap where a pinned pair with no inbound traffic would otherwise never migrate. Idempotent: re-enabling is a no-op for the cap, and the offer election stays fail-closed. Returns how many migration offers were initiated.",{},async(r,n)=>W(e(n),o=>o.advertiseMigrate(),({wasAdvertising:o,advertising:s,offers:i})=>L(`advertise_migrate: core.e2e.migrate ${s?"advertised":"NOT advertised"}${o?" (already advertising \u2014 cap unchanged)":""}; ${i} migration offer(s) initiated to eligible e2e contact(s).`))),t.tool("set_persona","Set the bound identity's local operating contract (persona, free text). The persona is how the agent behaves when it adopts this identity; it is NEVER shared via invites \u2014 only via the control-plane cluster registry. An agent must ask the user before adopting a persona. Empty string clears it.",{persona:k.string().describe("The new persona text (empty string clears it).")},async({persona:r},n)=>W(e(n),o=>o.setPersona({persona:r}),o=>L(`Updated the persona of "${o.identity}".`)))}function Gp(t,e,r,n={}){let o=new ta({name:"ours",version:e},{capabilities:{logging:{}}}),s=typeof t=="function"?t:()=>t;return n.networkHostFiles&&o.resource("application-identities","ours://application-identities",{mimeType:"application/json",description:"Application-visible identities for this daemon instance."},async i=>({contents:[{uri:i.href,mimeType:"application/json",text:JSON.stringify({identities:await r.list()})}]})),hb(o,s,r,n),rb(o,s),yb(o,s),gb(o,s,n),eb(o,s),cb(o,s,n),pb(o,s),o}function JR(t,e,r){let n;try{n=t()}catch(o){r(e,o);return}n&&typeof n.then=="function"&&n.then(void 0,o=>r(e,o))}function _b(t,e,r){JR(()=>t.sendLoggingMessage({level:"info",logger:"ours",data:e}),"sendLoggingMessage",r)}var vb=null,Wp=t=>{try{process.stderr.write(`ours: ${t}
|
|
81
|
-
`,e=>{e&&vb?.()})}catch{vb?.()}};var sa=class{constructor(e,r){this.client=e;this.server=r}stopped=!1;bound=null;abort=new AbortController;stop(){this.stopped=!0,this.abort.abort()}async run(){for(;!this.stopped;){if(this.bound===null){let r=Wo(this.client);if(r===null){await new Promise(n=>setTimeout(n,250));continue}this.bound=r,Wp(`[${r}] watching for arrivals`)}let e=this.bound;try{for await(let r of this.client.watchNotifications(e,{kinds:["inbound"],signal:this.abort.signal})){if(this.stopped)return;let n=r,o=n.event==="file_received"?`new file ${n.filename??"?"} from ${n.from??"?"}`:`new message from ${n.from??"?"}`;if(_b(this.server,o,(s,i)=>Wp(`[${e}] ${s} failed: ${String(i)}`)),Wo(this.client)!==e){this.bound=null;break}}}catch(r){if(this.stopped)return;Wp(`[${e}] arrival watch stopped (${String(r)}) \u2014 tool calls still work; retrying`),this.bound=null,await new Promise(n=>setTimeout(n,1e3))}}}};var KR="1.2.0-nightly.
|
|
81
|
+
`,e=>{e&&vb?.()})}catch{vb?.()}};var sa=class{constructor(e,r){this.client=e;this.server=r}stopped=!1;bound=null;abort=new AbortController;stop(){this.stopped=!0,this.abort.abort()}async run(){for(;!this.stopped;){if(this.bound===null){let r=Wo(this.client);if(r===null){await new Promise(n=>setTimeout(n,250));continue}this.bound=r,Wp(`[${r}] watching for arrivals`)}let e=this.bound;try{for await(let r of this.client.watchNotifications(e,{kinds:["inbound"],signal:this.abort.signal})){if(this.stopped)return;let n=r,o=n.event==="file_received"?`new file ${n.filename??"?"} from ${n.from??"?"}`:`new message from ${n.from??"?"}`;if(_b(this.server,o,(s,i)=>Wp(`[${e}] ${s} failed: ${String(i)}`)),Wo(this.client)!==e){this.bound=null;break}}}catch(r){if(this.stopped)return;Wp(`[${e}] arrival watch stopped (${String(r)}) \u2014 tool calls still work; retrying`),this.bound=null,await new Promise(n=>setTimeout(n,1e3))}}}};var KR="1.2.0-nightly.3";function FD(t){let e=Bs(t?.profile);if(typeof t?.applicationConfigPath!="string"||t.applicationConfigPath===""||!t.applicationConfigPath.startsWith("/")||GR(t.applicationConfigPath)!==t.applicationConfigPath)throw new Error("Network MCP applicationConfigPath must be a normalized absolute path.");let r=new uo({instanceId:e.expectedInstanceId},{path:t.applicationConfigPath}),n=new WeakMap,o=new Map,s=i=>{i.closed||!i.sessionId||!i.client||i.watcher||i.client.then(a=>{if(i.closed||i.watcher)return;let c=new sa(a,i.server);i.watcher=c,c.run()}).catch(a=>{process.stderr.write(`ours: network MCP notification attachment failed: ${String(a)}
|
|
82
82
|
`)})};return{createServer(i){let a,u=Gp(()=>{if(i.sessionMode()!=="external")throw new Error("Network MCP requires external session mode.");let l=i.leaseToken();if(!l)throw new Error("Network MCP requires an external owner.");return a.client||(a.client=WR({...e,sessionMode:"external",leaseToken:l,env:{}})),s(a),a.client},KR,r,{networkHostFiles:!0});return a={context:i,server:u,closed:!1},n.set(u,a),u},createTransport(i){return new Hs(i)},isInitializeRequest:eo,onSessionInitialized(i,a){let c=n.get(a);if(!c)throw new Error("Network MCP initialized an unknown server.");c.sessionId=i,o.set(i,c),s(c)},onSessionClosed(i){let a=o.get(i);a&&(o.delete(i),n.delete(a.server),a.closed=!0,a.watcher?.stop(),a.client?.then(c=>c.close()).catch(()=>{}))}}}export{FD as createNetworkMcpIntegration};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/mcp",
|
|
3
|
-
"version": "1.2.0-nightly.
|
|
3
|
+
"version": "1.2.0-nightly.3",
|
|
4
4
|
"description": "MCP adapter for the shared ours daemon, with application-local identity bookkeeping and secure agent-to-agent messaging tools over stdio.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|