@ours.network/mcp 1.1.1 → 1.2.0-nightly.10
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 +89 -7
- package/dist/application-identities.js +2 -2
- package/dist/cli.js +5 -4
- package/dist/connector.js +37 -35
- package/dist/container.js +6 -0
- package/dist/host-client.js +2 -0
- package/dist/host-profile.js +1 -0
- package/dist/native-session.js +2 -0
- package/dist/server.js +32 -31
- package/dist/types/application-identities.d.ts +26 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/connector.d.ts +26 -0
- package/dist/types/contacts.d.ts +12 -0
- package/dist/types/container.d.ts +1 -0
- package/dist/types/host-client/index.d.ts +3 -0
- package/dist/types/host-client/native-session.d.ts +1 -0
- package/dist/types/host-client/profile.d.ts +2 -0
- package/dist/types/host-profile.d.ts +19 -0
- package/dist/types/mcp/file-context.d.ts +17 -0
- package/dist/types/mcp/format.d.ts +20 -0
- package/dist/types/mcp/push.d.ts +17 -0
- package/dist/types/mcp/registry.d.ts +22 -0
- package/dist/types/mcp/server.d.ts +23 -0
- package/dist/types/mcp/tool.d.ts +22 -0
- package/dist/types/mcp/tools/commands.d.ts +3 -0
- package/dist/types/mcp/tools/contacts.d.ts +3 -0
- package/dist/types/mcp/tools/files.d.ts +7 -0
- package/dist/types/mcp/tools/history.d.ts +3 -0
- package/dist/types/mcp/tools/identity.d.ts +6 -0
- package/dist/types/mcp/tools/messaging.d.ts +4 -0
- package/dist/types/mcp/tools/profile.d.ts +3 -0
- package/dist/types/native-session.d.ts +9 -0
- package/dist/types/session-guard.d.ts +13 -0
- package/package.json +19 -6
package/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
The agent-facing MCP adapter for the shared ours daemon.
|
|
4
4
|
|
|
5
5
|
`ours-mcp` does not contain, start, configure, or install a daemon. Install
|
|
6
|
-
`@ours.network/
|
|
7
|
-
single shared service with `ours
|
|
6
|
+
`@ours.network/daemon`, configure it with `ours-daemon config setup`, and start the
|
|
7
|
+
single shared service with `ours-daemon start` (or `ours-daemon install-service`).
|
|
8
8
|
|
|
9
9
|
## MCP configuration
|
|
10
10
|
|
|
@@ -28,6 +28,45 @@ 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
|
+
The plugins include `@ours.network/mcp` and launch its local stdio server.
|
|
32
|
+
Tools call the selected daemon through `@ours.network/sdk` using the existing
|
|
33
|
+
authenticated HTTP API. The profile contains `endpoint`, `expectedInstanceId`,
|
|
34
|
+
and `credentialPath`; the endpoint can be localhost or a remote origin.
|
|
35
|
+
No remote MCP endpoint is used. The daemon does not need the MCP package.
|
|
36
|
+
|
|
37
|
+
Local file paths are interpreted by the local MCP process: `send_file` uploads
|
|
38
|
+
local bytes through the SDK, `save_file` downloads to local disk, and
|
|
39
|
+
`define_local_identity_file` writes the local workspace file. Native session
|
|
40
|
+
records and application identity visibility remain on the client host.
|
|
41
|
+
|
|
42
|
+
Host-client helpers select explicit `OURS_CONFIG` first, otherwise the private
|
|
43
|
+
`~/.ours-client/profile.json` when present, otherwise their existing unmanaged
|
|
44
|
+
configuration. The managed file contains the same
|
|
45
|
+
`endpoint`/`expectedInstanceId`/`credentialPath` tuple; an invalid or unreadable
|
|
46
|
+
managed profile is an error, never a fallback to a local daemon. This selection
|
|
47
|
+
also applies to the helpers bundled in Codex and Claude. It does not change the
|
|
48
|
+
SDK daemon configuration default or application/native-session state.
|
|
49
|
+
|
|
50
|
+
When a complete external host profile is selected, the stdio server
|
|
51
|
+
starts without allocating an owner. Its first tool call selects the native
|
|
52
|
+
logical session from Codex request `_meta.threadId`, or from the existing
|
|
53
|
+
`CLAUDE_CODE_SESSION_ID` fallback when that value identifies the Claude
|
|
54
|
+
session. Initialization and tool discovery do not need owner metadata. The
|
|
55
|
+
connector stores a random owner UUID in private recovery metadata below the
|
|
56
|
+
ours-mcp config directory and recovers that owner after an MCP process recycle.
|
|
57
|
+
Different native session selectors keep separate clients, bindings and arrival
|
|
58
|
+
watches. Closing stdio closes transport/watch resources; it does not release
|
|
59
|
+
the logical owner.
|
|
60
|
+
|
|
61
|
+
The existing `session-end` hook command reads `session_id` from its JSON stdin.
|
|
62
|
+
For a host profile it persists that owner's exact terminal intent before asking
|
|
63
|
+
the daemon to release the lease, and records the end only after the release is
|
|
64
|
+
acknowledged. A pending release is retried before any successor owner can be
|
|
65
|
+
allocated. A later call for an ended selector receives a fresh owner UUID.
|
|
66
|
+
If the terminal hook is disabled or never delivered, no terminal cleanup is
|
|
67
|
+
claimed: ordinary MCP recycle and resume recover the existing owner, and its
|
|
68
|
+
protected resources remain available.
|
|
69
|
+
|
|
31
70
|
## Application identity list
|
|
32
71
|
|
|
33
72
|
The daemon hosts all identities. ours-mcp keeps only the identities adopted by
|
|
@@ -56,8 +95,8 @@ names remain recorded but are not rendered; idempotent close/remove cleans them.
|
|
|
56
95
|
## Compatibility CLI
|
|
57
96
|
|
|
58
97
|
Former ours-mcp lifecycle entry points remain compatibility aliases that
|
|
59
|
-
delegate argv, stdio, and exit status to `ours
|
|
60
|
-
must be on `PATH`, or its exact path may be provided through `
|
|
98
|
+
delegate argv, stdio, and exit status to `ours-daemon`. The `ours-daemon` executable
|
|
99
|
+
must be on `PATH`, or its exact path may be provided through `OURS_DAEMON_CLI`. No
|
|
61
100
|
command falls back to an embedded daemon.
|
|
62
101
|
|
|
63
102
|
`ours-mcp watch [identity]` streams inbound JSON Lines. With no identity argument,
|
|
@@ -79,9 +118,10 @@ that finds old packet state refuses startup without changing it. Operators may
|
|
|
79
118
|
back it up and must remove it themselves before starting clean; installers never
|
|
80
119
|
delete identity state implicitly.
|
|
81
120
|
|
|
82
|
-
The
|
|
83
|
-
|
|
84
|
-
|
|
121
|
+
The SDK dependency is pinned to the published thin client `3.8.1-nightly.9`.
|
|
122
|
+
Native integration tests use `@ours.network/daemon@3.8.1-nightly.1` as a development
|
|
123
|
+
dependency. Production MCP installs do not include a daemon; operators install
|
|
124
|
+
and manage `ours-daemon` separately.
|
|
85
125
|
|
|
86
126
|
## Typed commands
|
|
87
127
|
|
|
@@ -101,3 +141,45 @@ npm run build --workspace @ours.network/mcp
|
|
|
101
141
|
npm run typecheck --workspace @ours.network/mcp
|
|
102
142
|
npm test --workspace @ours.network/mcp
|
|
103
143
|
```
|
|
144
|
+
|
|
145
|
+
### External-profile verification
|
|
146
|
+
|
|
147
|
+
The existing host-profile checks are part of `npm test`. Build the core package
|
|
148
|
+
and prepare the selected SDK/CLI artifacts first. For the daemon-backed native
|
|
149
|
+
selector, proxy-recycle, SessionEnd and token-refresh checks, run from this package:
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
OURS_TEST_DAEMON_CLI=/absolute/path/to/selected/cli/dist/cli.js npm run test:external-profile
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Use an isolated Docker environment and test-owned state. These tests do not log
|
|
156
|
+
in to Codex/Claude and do not replace actual native harness acceptance.
|
|
157
|
+
|
|
158
|
+
### Container entrypoint
|
|
159
|
+
|
|
160
|
+
The packaged `dist/container.js` entrypoint is used only when a host integration
|
|
161
|
+
explicitly selects a Compose profile. It preserves daemon/profile validation,
|
|
162
|
+
application visibility, file transfer and notification operations. Standard
|
|
163
|
+
`ours-mcp proxy` and host-only installations do not require Docker. The entrypoint
|
|
164
|
+
uses the daemon state directory (`OURS_STATE_DIR`, default `/var/lib/ours`) and its
|
|
165
|
+
private `.mcp` profile; it is an internal launcher interface, not an additional
|
|
166
|
+
service or a replacement for normal host startup.
|
|
167
|
+
|
|
168
|
+
### Remote clients over HTTPS
|
|
169
|
+
|
|
170
|
+
A client may select an `https://` origin backed by a TLS reverse proxy. Keep the
|
|
171
|
+
daemon listener private and configure the proxy separately with a certificate
|
|
172
|
+
trusted by the client and matching the hostname. Node uses its normal trust
|
|
173
|
+
store; a private CA may be supplied through `NODE_EXTRA_CA_CERTS` before starting
|
|
174
|
+
the client. Certificate verification must remain enabled.
|
|
175
|
+
|
|
176
|
+
The existing issued client credential works over either transport; changing the
|
|
177
|
+
URL scheme does not require a new credential. Keep the server's master on the
|
|
178
|
+
server. Forward `x-ours-api-token` and the `x-ours-*` session headers unchanged,
|
|
179
|
+
and support streamed request/response bodies and long polling. Client requests
|
|
180
|
+
refuse redirects, including HTTPS-to-HTTP redirects: configure the final HTTPS
|
|
181
|
+
origin directly. UUID/capability checks still precede credential-bearing calls.
|
|
182
|
+
|
|
183
|
+
This adds client HTTPS support, not an HTTPS daemon listener, certificate
|
|
184
|
+
provisioning or automatic reverse-proxy configuration. Existing local HTTP and
|
|
185
|
+
SSH-tunnel profiles continue to work.
|
|
@@ -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{
|
|
3
|
-
`,{mode:384,flag:"wx"}),await
|
|
4
|
-
|
|
5
|
-
`)},
|
|
2
|
+
import{readFileSync as kt,realpathSync as V}from"node:fs";import{fileURLToPath as xt}from"node:url";import{randomUUID as z}from"node:crypto";import{chmod as K,mkdir as Q,readFile as Z,rename as tt,unlink as et,writeFile as nt}from"node:fs/promises";import{homedir as it}from"node:os";import{dirname as rt,resolve as u}from"node:path";var C=1,ot="OURS_MCP_CONFIG",R=/^[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 m(t=process.env){let e=(t[ot]??"").trim();return u(e||u(it(),".ours-mcp","config.json"))}function b(t,e,n,i){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 r={};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 c=s;if(!Array.isArray(c.identities)||c.identities.some(d=>typeof d!="string"||d.length===0))throw new Error(`Invalid ours-mcp application identity config at ${e}: ${n} ${JSON.stringify(o)} has an invalid identity list.`);r[i(o)]={identities:[...new Set(c.identities)].sort()}}return r}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 i=n;if(i.version!==C)throw new Error(`Unsupported ours-mcp application identity config version at ${e}: expected ${C}, found ${String(i.version)}.`);let r=b(i.daemons,e,"daemon",u),o=i.instances===void 0?{}:b(i.instances,e,"instance",s=>{if(!R.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:r,instances:o}}async function I(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=rt(t);await Q(n,{recursive:!0,mode:448});let i=`${t}.${process.pid}.${z()}.tmp`;try{await nt(i,`${JSON.stringify(e,null,2)}
|
|
3
|
+
`,{mode:384,flag:"wx"}),await tt(i,t),await K(t,384)}catch(r){try{await et(i)}catch{}throw r}}var f=class{stateDir;instanceId;path;constructor(e,n={}){if(typeof e=="string")this.stateDir=u(e),this.instanceId=null;else{if(!e||!R.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??m(n.env))}rows(e){return this.instanceId===null?e.daemons:e.instances}key(){return this.instanceId??this.stateDir}async list(){let e=await I(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 I(this.path),i=this.rows(n),r=this.key(),o=i[r]?.identities??[];o.includes(e)||(i[r]={identities:[...o,e].sort()},await _(this.path,n))}async remove(e){let n=await I(this.path),i=this.rows(n),r=this.key(),o=i[r];if(!o?.identities.includes(e))return;let s=o.identities.filter(c=>c!==e);s.length===0?delete i[r]:i[r]={identities:s},await _(this.path,n)}};async function A(t,e){let n=new Set(await t.list());return e.filter(i=>n.has(i.name))}import{readGatewayClientProfile as ct,validateGatewayClientProfile as dt}from"@ours.network/sdk/client";var E=dt;function lt(t=process.env){let{configPath:e,...n}=ct(t);return{profile:n,configPath:e}}function w(t=process.env){return lt(t).profile}import{createHash as vt,randomUUID as L}from"node:crypto";import{chmod as U,mkdir as It,readFile as Et,rename as St,unlink as Pt,writeFile as Nt}from"node:fs/promises";import{dirname as P,join as Ot,resolve as Ct}from"node:path";import{attachOursClient as bt}from"@ours.network/sdk";import{createHash as pt}from"node:crypto";import{openSync as ut,closeSync as ft,lstatSync as S,mkdirSync as mt,chmodSync as wt,constants as g}from"node:fs";import{join as D}from"node:path";import{createRequire as $}from"node:module";var gt=5e3,p=null,k=!1;function yt(){if(p)return p;if(k)throw new Error("No SQLite implementation available for native session guard");k=!0;try{return p=$(import.meta.url)("node:sqlite").DatabaseSync,p}catch{try{let t=$(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 ht(t){try{let n=ut(t,g.O_RDWR|g.O_CREAT|g.O_EXCL|g.O_NOFOLLOW,384);ft(n)}catch(n){if(n.code!=="EEXIST")throw n}let e=S(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 x(t){let e=process.getuid(),n=D(`/tmp/ours-${e}`,"session-guards");mt(n,{recursive:!0,mode:448});try{wt(n,448)}catch{}let i=pt("sha256").update(t).digest("hex"),r=D(n,`${i}.guard`);ht(r);let o=S(r),s=yt(),c=new s(r);try{let d=S(r);if(o.dev!==d.dev||o.ino!==d.ino||o.mode!==d.mode||o.uid!==d.uid)throw new Error("Native session guard file changed during acquisition");return c.exec(`PRAGMA busy_timeout=${gt}; BEGIN EXCLUSIVE`),()=>{try{c.close()}catch{}}}catch(d){try{c.close()}catch{}throw d}}var v=1,j=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,l=new Map,y=new Map;function _t(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 J(t,e,n){if(!t||!t.startsWith("/")||Ct(t)!==t)throw new Error("Native session host record root must be a normalized absolute path.");if(!j.test(e))throw new Error("Native session daemon instance must be a lowercase UUID.");let i=vt("sha256").update(_t(n),"utf8").digest("hex");return Ot(t,"sessions",e,`${i}.json`)}function Rt(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 i=n;if(Object.keys(i).sort().join(",")!=="ownerInstanceId,state,version"||i.version!==v||typeof i.ownerInstanceId!="string"||!j.test(i.ownerInstanceId)||!["active","pending","ended"].includes(String(i.state)))throw new Error(`Invalid native session record at ${e}.`);return i}async function h(t){try{return Rt(await Et(t,"utf8"),t)}catch(e){if(e.code==="ENOENT")return null;throw e}}async function N(t,e){let n=P(t);await It(n,{recursive:!0,mode:448}),await U(n,448);let i=`${t}.${process.pid}.${L()}.tmp`;try{await Nt(i,`${JSON.stringify(e,null,2)}
|
|
4
|
+
`,{mode:384,flag:"wx"}),await St(i,t),await U(t,384)}catch(r){try{await Pt(i)}catch{}throw r}}async function F(t,e){let n=y.get(t)??Promise.resolve(),i,r=new Promise(c=>{i=c}),o=n.catch(()=>{}).then(()=>r);y.set(t,o),await n.catch(()=>{});let s;try{return s=x(t),await e()}finally{try{s?.()}finally{i(),y.get(t)===o&&y.delete(t)}}}function H(t,e){return bt({endpoint:t.endpoint,expectedInstanceId:t.expectedInstanceId,credentialPath:t.credentialPath,sessionMode:"external",leaseToken:e,env:{}})}function T(t,e,n){let i=l.get(t);if(i?.ownerInstanceId===n)return i.promise;i&&(l.delete(t),i.promise.then(s=>s.close()).catch(()=>{}));let r=H(e,n),o={ownerInstanceId:n,promise:r};return l.set(t,o),r.catch(()=>{l.get(t)===o&&l.delete(t)}),r}async function At(t,e,n){let i=l.get(t),r=i?.ownerInstanceId===n?await i.promise:await H(e,n);try{await r.releaseLease()}finally{i?.ownerInstanceId!==n&&await r.close()}}async function M(t,e,n){await At(t,e,n);let i=await h(t);i?.state==="pending"&&i.ownerInstanceId===n&&await N(t,{version:v,ownerInstanceId:n,state:"ended"});let r=l.get(t);r?.ownerInstanceId===n&&(l.delete(t),r.promise.then(o=>o.close()).catch(()=>{}))}async function G(t,e,n=process.env){return Dt(t,e,P(m(n)))}async function Dt(t,e,n){let i=E(t),r=J(n,i.expectedInstanceId,e);return F(r,async()=>{let o=await h(r);if(o?.state==="pending"&&(await M(r,i,o.ownerInstanceId),o=await h(r)),o?.state==="active")return T(r,i,o.ownerInstanceId);let s=L();return await N(r,{version:v,ownerInstanceId:s,state:"active"}),T(r,i,s)})}async function q(t,e,n=process.env){return $t(t,e,P(m(n)))}async function $t(t,e,n){let i=E(t),r=J(n,i.expectedInstanceId,e);await F(r,async()=>{let o=await h(r);o===null||o.state==="ended"||(o.state==="active"&&(o={version:v,ownerInstanceId:o.ownerInstanceId,state:"pending"},await N(r,o)),await M(r,i,o.ownerInstanceId))})}var O="1.2.0-nightly.10",a=t=>{process.stdout.write(`${t}
|
|
5
|
+
`)},X=t=>{process.stderr.write(`${t}
|
|
6
|
+
`)},W=t=>Number.isInteger(t)&&t>1,B=Number(process.env.OURS_CLIENT_PID),Y=W(B)?B:W(process.ppid)?process.ppid:process.pid,Ut=(process.env.CLAUDE_CODE_SESSION_ID??"").trim()||`ours-mcp:${Y}`,Tt=(process.env.OURS_BIND_IDENTITY??"").trim()||void 0,Lt=new Set(["start","stop","restart","serve","run","status","install-service","uninstall-service"]);function jt(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; configure ~/.ours-client/profile.json with the gateway URL.")}async function Jt(){let t=new URL("./connector.js",import.meta.url),{runConnector:e}=await import(t.href);await e({leaseToken:Ut,clientPid:Y,version:O,bindIdentity:Tt})}async function Ft(){let t=w(process.env),e;try{e=JSON.parse(kt(0,"utf8"))}catch{throw new Error("SessionEnd requires valid hook JSON on stdin.")}let n=e?.session_id;if(typeof n!="string"||!n.trim())throw new Error("SessionEnd hook input must contain a non-empty session_id.");await q(t,n,process.env)}function Ht(t,e){return JSON.stringify({identity:t,...e})}async function Mt(t,e,n){for await(let i of t.watchNotifications(e,{kinds:["inbound"],signal:n}))a(Ht(e,i))}async function Gt(t){if(t.length>1||t[0]?.startsWith("-"))throw new Error("Usage: ours-mcp watch [identity]");let e=w(process.env),n=new f({instanceId:e.expectedInstanceId});await n.list();let i=await G(e,(process.env.CLAUDE_CODE_SESSION_ID??"").trim()),r=t[0]?.trim(),o;r?o=[r]:o=(await A(n,await i.identities())).map(c=>c.name);let s=new AbortController;process.once("SIGINT",()=>s.abort()),process.once("SIGTERM",()=>s.abort()),X(`ours-mcp watch: ${o.length===0?"no application identities":`watching ${o.join(", ")}`}`);try{await Promise.all(o.map(c=>Mt(i,c,s.signal)))}finally{await i.close()}}function qt(){a(`ours-mcp ${O} \u2014 MCP adapter for the shared ours daemon`),a(""),a("Usage: ours-mcp <command> [options]"),a(" verify-client-profile validate the shared gateway profile (no network access)"),a(" proxy run the stdio MCP server (never starts a daemon)"),a(" session-end release this harness session and clean up its temporary identities"),a(" watch [identity] stream inbound JSON Lines; without a name, only ours-mcp identities"),a(" start|stop|restart|serve|status"),a(" install-service|uninstall-service"),a(" compatibility aliases for `ours-daemon <command>`"),a(" version print the ours-mcp package version"),a(""),a("Daemon administration uses @ours.network/daemon; API commands use @ours.network/cli:"),a(" ours-daemon config setup"),a(" ours identity --help")}async function Vt(){let t=process.argv.slice(2);jt(t);let e=t.shift()??"help";if(Lt.has(e))throw new Error("Server lifecycle commands are not client operations. Manage the server with ours-install server; configure clients with ours-install client.");switch(e){case"verify-client-profile":if(t.length)throw new Error("Usage: ours-mcp verify-client-profile");w(process.env),a("ours.gateway-client-profile-v1");return;case"proxy":if(t.length)throw new Error("Usage: ours-mcp proxy");await Jt();return;case"session-end":if(t.length)throw new Error("Usage: ours-mcp session-end");await Ft();return;case"watch":await Gt(t);return;case"setup":throw new Error("ours-mcp no longer owns daemon configuration. Run `ours-daemon 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":a(O);return;case"help":case"--help":case"-h":qt();return;default:throw new Error(`Unknown command ${JSON.stringify(e)}. Run \`ours-mcp --help\`.`)}}function Wt(){if(!process.argv[1])return!1;try{return V(process.argv[1])===V(xt(import.meta.url))}catch{return!1}}var Bt=Wt();Bt&&Vt().catch(t=>{X(`ours-mcp error: ${t instanceof Error?t.message:String(t)}`),process.exitCode=1});
|