@ours.network/mcp 1.2.0-nightly.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 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/cli@2.2.0`, configure it with `ours config setup`, and start the
7
- single shared service with `ours daemon start` (or `ours daemon install-service`).
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,37 +28,16 @@ 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:
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.
35
36
 
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.
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.
62
41
 
63
42
  Host-client helpers select explicit `OURS_CONFIG` first, otherwise the private
64
43
  `~/.ours-client/profile.json` when present, otherwise their existing unmanaged
@@ -116,8 +95,8 @@ names remain recorded but are not rendered; idempotent close/remove cleans them.
116
95
  ## Compatibility CLI
117
96
 
118
97
  Former ours-mcp lifecycle entry points remain compatibility aliases that
119
- delegate argv, stdio, and exit status to `ours daemon`. The `ours` executable
120
- must be on `PATH`, or its exact path may be provided through `OURS_CLI`. No
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
121
100
  command falls back to an embedded daemon.
122
101
 
123
102
  `ours-mcp watch [identity]` streams inbound JSON Lines. With no identity argument,
@@ -139,9 +118,10 @@ that finds old packet state refuses startup without changing it. Operators may
139
118
  back it up and must remove it themselves before starting clean; installers never
140
119
  delete identity state implicitly.
141
120
 
142
- The external-history and typed-command integration requires the published `@ours.network/sdk@3.7.0`
143
- and `@ours.network/cli@2.2.0` artifacts. Both are pinned exactly so registry-only
144
- installs use the validated contract; there is no compatibility fallback.
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.
145
125
 
146
126
  ## Typed commands
147
127
 
@@ -184,3 +164,22 @@ application visibility, file transfer and notification operations. Standard
184
164
  uses the daemon state directory (`OURS_STATE_DIR`, default `/var/lib/ours`) and its
185
165
  private `.mcp` profile; it is an internal launcher interface, not an additional
186
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.
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
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});
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});