@ours.network/mcp 1.1.0 → 1.2.0-nightly.1
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 +83 -0
- package/dist/application-identities.js +2 -2
- package/dist/cli.js +5 -4
- package/dist/connector.js +37 -36
- package/dist/container.js +6 -0
- package/dist/host-client.js +3 -0
- package/dist/host-profile.js +1 -0
- package/dist/native-session.js +2 -0
- package/dist/network-integration.js +82 -0
- package/dist/server.js +31 -31
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -28,6 +28,66 @@ Legacy daemon variables such as `OURS_AUTOSTART`, `OURS_TRANSPORT`, and
|
|
|
28
28
|
`OURS_UNIT_DIR` are rejected. Named `--application` selections are also rejected;
|
|
29
29
|
use the SDK daemon selection variables instead.
|
|
30
30
|
|
|
31
|
+
For an installer-selected server profile, `@ours.network/cli` loads the fixed
|
|
32
|
+
`@ours.network/mcp/network-integration` export and mounts these tools on the
|
|
33
|
+
daemon's existing authenticated `/mcp` listener. The installer writes one
|
|
34
|
+
`networkMcp` object in the daemon config:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"networkMcp": {
|
|
39
|
+
"profile": {
|
|
40
|
+
"endpoint": "http://127.0.0.1:3050",
|
|
41
|
+
"expectedInstanceId": "56bd6f1f-2df1-4844-9a46-d558c021e00f",
|
|
42
|
+
"credentialPath": "/private/ours/server-client-token"
|
|
43
|
+
},
|
|
44
|
+
"applicationConfigPath": "/private/ours/application-identities.json"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The profile credential is an issued client credential. The integration never
|
|
50
|
+
reads the server's master secret, starts another listener, or attaches before
|
|
51
|
+
the first tool call. It exposes the selected instance's application names at
|
|
52
|
+
`ours://application-identities` and keeps arrival watches scoped to each real
|
|
53
|
+
MCP transport.
|
|
54
|
+
|
|
55
|
+
In this network mode, a host bridge stages `send_file.path` through the SDK byte
|
|
56
|
+
upload route. `save_file` returns an `oursHostSave` intent for that bridge, and
|
|
57
|
+
final success is produced only after the host has downloaded and written the
|
|
58
|
+
bytes. The shared profile, native-owner-record and file-adapter source lives in
|
|
59
|
+
`src/host-client/` so client packages can bundle it into their own entrypoints.
|
|
60
|
+
Native records receive their host-private root explicitly; they do not derive a
|
|
61
|
+
client path from the server application config.
|
|
62
|
+
|
|
63
|
+
Host-client helpers select explicit `OURS_CONFIG` first, otherwise the private
|
|
64
|
+
`~/.ours-client/profile.json` when present, otherwise their existing unmanaged
|
|
65
|
+
configuration. The managed file contains the same
|
|
66
|
+
`endpoint`/`expectedInstanceId`/`credentialPath` tuple; an invalid or unreadable
|
|
67
|
+
managed profile is an error, never a fallback to a local daemon. This selection
|
|
68
|
+
also applies to the helpers bundled in Codex and Claude. It does not change the
|
|
69
|
+
SDK daemon configuration default or application/native-session state.
|
|
70
|
+
|
|
71
|
+
When a complete external host profile is selected, the stdio server
|
|
72
|
+
starts without allocating an owner. Its first tool call selects the native
|
|
73
|
+
logical session from Codex request `_meta.threadId`, or from the existing
|
|
74
|
+
`CLAUDE_CODE_SESSION_ID` fallback when that value identifies the Claude
|
|
75
|
+
session. Initialization and tool discovery do not need owner metadata. The
|
|
76
|
+
connector stores a random owner UUID in private recovery metadata below the
|
|
77
|
+
ours-mcp config directory and recovers that owner after an MCP process recycle.
|
|
78
|
+
Different native session selectors keep separate clients, bindings and arrival
|
|
79
|
+
watches. Closing stdio closes transport/watch resources; it does not release
|
|
80
|
+
the logical owner.
|
|
81
|
+
|
|
82
|
+
The existing `session-end` hook command reads `session_id` from its JSON stdin.
|
|
83
|
+
For a host profile it persists that owner's exact terminal intent before asking
|
|
84
|
+
the daemon to release the lease, and records the end only after the release is
|
|
85
|
+
acknowledged. A pending release is retried before any successor owner can be
|
|
86
|
+
allocated. A later call for an ended selector receives a fresh owner UUID.
|
|
87
|
+
If the terminal hook is disabled or never delivered, no terminal cleanup is
|
|
88
|
+
claimed: ordinary MCP recycle and resume recover the existing owner, and its
|
|
89
|
+
protected resources remain available.
|
|
90
|
+
|
|
31
91
|
## Application identity list
|
|
32
92
|
|
|
33
93
|
The daemon hosts all identities. ours-mcp keeps only the identities adopted by
|
|
@@ -101,3 +161,26 @@ npm run build --workspace @ours.network/mcp
|
|
|
101
161
|
npm run typecheck --workspace @ours.network/mcp
|
|
102
162
|
npm test --workspace @ours.network/mcp
|
|
103
163
|
```
|
|
164
|
+
|
|
165
|
+
### External-profile verification
|
|
166
|
+
|
|
167
|
+
The existing host-profile checks are part of `npm test`. Build the core package
|
|
168
|
+
and prepare the selected SDK/CLI artifacts first. For the daemon-backed native
|
|
169
|
+
selector, proxy-recycle, SessionEnd and token-refresh checks, run from this package:
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
OURS_TEST_DAEMON_CLI=/absolute/path/to/selected/cli/dist/cli.js npm run test:external-profile
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Use an isolated Docker environment and test-owned state. These tests do not log
|
|
176
|
+
in to Codex/Claude and do not replace actual native harness acceptance.
|
|
177
|
+
|
|
178
|
+
### Container entrypoint
|
|
179
|
+
|
|
180
|
+
The packaged `dist/container.js` entrypoint is used only when a host integration
|
|
181
|
+
explicitly selects a Compose profile. It preserves daemon/profile validation,
|
|
182
|
+
application visibility, file transfer and notification operations. Standard
|
|
183
|
+
`ours-mcp proxy` and host-only installations do not require Docker. The entrypoint
|
|
184
|
+
uses the daemon state directory (`OURS_STATE_DIR`, default `/var/lib/ours`) and its
|
|
185
|
+
private `.mcp` profile; it is an internal launcher interface, not an additional
|
|
186
|
+
service or a replacement for normal host startup.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{randomUUID as
|
|
2
|
-
`,{mode:384,flag:"wx"}),await
|
|
1
|
+
import{randomUUID as w}from"node:crypto";import{chmod as I,mkdir as m,readFile as h,rename as v,unlink as A,writeFile as E}from"node:fs/promises";import{homedir as $}from"node:os";import{dirname as N,resolve as c}from"node:path";var y=1,S="OURS_MCP_CONFIG",u=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,C=()=>({version:1,daemons:{},instances:{}});function P(e=process.env){let t=(e[S]??"").trim();return c(t||c($(),".ours-mcp","config.json"))}function l(e,t,i,n){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`Invalid ours-mcp application identity config at ${t}: "${i}s" must be an object.`);let r={};for(let[o,s]of Object.entries(e)){if(!s||typeof s!="object"||Array.isArray(s))throw new Error(`Invalid ours-mcp application identity config at ${t}: ${i} ${JSON.stringify(o)} must be an object.`);let a=s;if(!Array.isArray(a.identities)||a.identities.some(p=>typeof p!="string"||p.length===0))throw new Error(`Invalid ours-mcp application identity config at ${t}: ${i} ${JSON.stringify(o)} has an invalid identity list.`);r[n(o)]={identities:[...new Set(a.identities)].sort()}}return r}function k(e,t){let i;try{i=JSON.parse(e)}catch(s){throw new Error(`Invalid ours-mcp application identity config at ${t}: ${String(s)}`)}if(!i||typeof i!="object"||Array.isArray(i))throw new Error(`Invalid ours-mcp application identity config at ${t}: expected an object.`);let n=i;if(n.version!==y)throw new Error(`Unsupported ours-mcp application identity config version at ${t}: expected ${y}, found ${String(n.version)}.`);let r=l(n.daemons,t,"daemon",c),o=n.instances===void 0?{}:l(n.instances,t,"instance",s=>{if(!u.test(s))throw new Error(`Invalid ours-mcp application identity config at ${t}: instance ${JSON.stringify(s)} must be a lowercase UUID.`);return s});return{version:1,daemons:r,instances:o}}async function d(e){try{return k(await h(e,"utf8"),e)}catch(t){if(t.code==="ENOENT")return C();throw t}}async function f(e,t){let i=N(e);await m(i,{recursive:!0,mode:448});let n=`${e}.${process.pid}.${w()}.tmp`;try{await E(n,`${JSON.stringify(t,null,2)}
|
|
2
|
+
`,{mode:384,flag:"wx"}),await v(n,e),await I(e,384)}catch(r){try{await A(n)}catch{}throw r}}var g=class{stateDir;instanceId;path;constructor(t,i={}){if(typeof t=="string")this.stateDir=c(t),this.instanceId=null;else{if(!t||!u.test(t.instanceId))throw new Error("Application identity instanceId must be a lowercase UUID.");this.stateDir=null,this.instanceId=t.instanceId}this.path=c(i.path??P(i.env))}rows(t){return this.instanceId===null?t.daemons:t.instances}key(){return this.instanceId??this.stateDir}async list(){let t=await d(this.path);return[...this.rows(t)[this.key()]?.identities??[]]}async has(t){return(await this.list()).includes(t)}async add(t){if(!t)throw new Error("Cannot add an empty identity name to ours-mcp.");let i=await d(this.path),n=this.rows(i),r=this.key(),o=n[r]?.identities??[];o.includes(t)||(n[r]={identities:[...o,t].sort()},await f(this.path,i))}async remove(t){let i=await d(this.path),n=this.rows(i),r=this.key(),o=n[r];if(!o?.identities.includes(t))return;let s=o.identities.filter(a=>a!==t);s.length===0?delete n[r]:n[r]={identities:s},await f(this.path,i)}};async function x(e,t){let i=new Set(await e.list());return t.filter(n=>i.has(n.name))}export{S as APPLICATION_IDENTITIES_ENV,y as APPLICATION_IDENTITIES_VERSION,g as ApplicationIdentityStore,P as applicationIdentityConfigPath,x as filterApplicationIdentities};
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{spawn as
|
|
3
|
-
`,{mode:384,flag:"wx"}),await
|
|
4
|
-
|
|
5
|
-
`)},
|
|
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
|
+
`,{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.1",c=t=>{process.stdout.write(`${t}
|
|
5
|
+
`)},K=t=>{process.stderr.write(`${t}
|
|
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});
|