@ours.network/mcp 0.17.0 → 0.18.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 CHANGED
@@ -1,123 +1,74 @@
1
1
  # @ours.network/mcp
2
2
 
3
- Agent-agnostic MCP server for **ours** a native ADAPT node exposing secure
4
- agent-to-agent messaging tools. It is platform-neutral (no Claude Code / Codex /
5
- Cursor specifics); per-platform plugins depend on this package and run its
6
- `proxy` to reach the daemon.
7
-
8
- The server **is** the node: on startup it boots a single ADAPT packet (a MUFL
9
- messenger), restores prior state from the state dir, connects to the broker, and
10
- exposes the messaging tools — each a thin wrapper over one MUFL user transaction:
11
-
12
- - `generate_invite` — invite to share out-of-band (optionally named; `mode`
13
- `"one_time"` default or `"public"` for a reusable open-posting invite)
14
- - `list_invites` / `revoke_invite` — outstanding invites; revocation is the only
15
- way to close a public invite (idempotent)
16
- - `add_contact` — add a contact from an invite blob (TOFU)
17
- - `list_contacts`
18
- - `remove_contact` — contacts-layer forget + one best-effort authenticated
19
- "remove me" notice to the peer (fire-and-forget; remote removal NOT guaranteed)
20
- - `create_temporary_identity` / `close_temporary_identity` — session-scoped
21
- identity owned by exactly one session lease: on close/session end, contacts get
22
- one best-effort remove-me notice, then ALL local state is deleted (stale ones —
23
- owner process dead — are swept automatically; permanent identities never are)
24
- - `send_message` — end-to-end encrypted; optional `reply_to_wire_id` (+ `reply_to_sentence`) to reply to a specific message
25
- - `get_messages` — return unread messages (bodies, each with its `wire_id` + any `reply_to`) + mark read; delivered exactly once
26
- - `mark_processed` / `defer_messages` — remove handled messages, or re-queue read ones for another session
27
- - `list_incoming_messages` — full inbox with ids + status (read-only)
28
- - `list_incoming_files` — byte-free structured preauthorization metadata, including authenticated sender CID (`from.id`), separate display name, stable IDs, size/date/status, filename and MIME
29
- - `get_files({ wire_ids? })` — atomically retrieve only approved unread wire IDs; omitting `wire_ids` preserves legacy all-unread retrieval. Results include safe local paths, actual size/hash, provenance/status, and structured voice transcription outcomes
30
-
31
- File wake events are content-free but correlation-complete: authenticated `sender_id`,
32
- `file_id`, `wire_id`, display name, filename, MIME, byte count, and received date. A caller
33
- must authorize against `sender_id`, never the untrusted display label. Selected IDs are
34
- unique 64-hex wire IDs (maximum 32); malformed, duplicate, unknown, or stale selections
35
- fail atomically without retrieving or writing any file.
36
-
37
- ## Configuration
38
-
39
- | Env var | Default | Meaning |
40
- |---------|---------|---------|
41
- | `OURS_STATE_DIR` | `~/.ours` | Node identity + serialized state. Distinct per node. |
42
- | `OURS_BROKER_URL` | `wss://broker1.ours.network` | The ADAPT broker to connect through. Set to `ws://localhost:9000` for a local broker. |
43
-
44
- ### Voice-message transcription
45
-
46
- Voice transcription is off until a provider and key (plus provider-required fields) are
47
- configured. Check readiness without exposing credentials:
3
+ The agent-facing MCP adapter for the shared ours daemon.
4
+
5
+ `ours-mcp` does not contain, start, configure, or install a daemon. Install
6
+ `@ours.network/cli@1.0.1`, configure it with `ours config setup`, and start the
7
+ single shared service with `ours daemon start` (or `ours daemon install-service`).
8
+
9
+ ## MCP configuration
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "ours": {
15
+ "command": "ours-mcp",
16
+ "args": ["proxy"]
17
+ }
18
+ }
19
+ }
20
+ ```
48
21
 
49
- ```sh
50
- ours-mcp voice-setup
51
- ours-mcp voice-status --json
22
+ `proxy` attaches through `@ours.network/sdk@2.0.1`. It uses the SDK's coherent
23
+ daemon selection (`OURS_CONFIG`, or matching `OURS_PORT` and `OURS_STATE_DIR`)
24
+ and verifies `/state-dir` before credentials are sent. An unavailable daemon is
25
+ reported with install/start guidance; it is never started inside the MCP process.
26
+
27
+ Legacy daemon variables such as `OURS_AUTOSTART`, `OURS_TRANSPORT`, and
28
+ `OURS_UNIT_DIR` are rejected. Named `--application` selections are also rejected;
29
+ use the SDK daemon selection variables instead.
30
+
31
+ ## Application identity list
32
+
33
+ The daemon hosts all identities. ours-mcp keeps only the identities adopted by
34
+ this application and filters global listings through that set. Fresh installs
35
+ start empty. Creating an identity or successfully calling `choose_identity`
36
+ adopts it; closing or removing one deletes it from the application list.
37
+
38
+ The file defaults to `~/.ours-mcp/config.json` and can be overridden for tests
39
+ with `OURS_MCP_CONFIG`. Its versioned shape is:
40
+
41
+ ```json
42
+ {
43
+ "version": 1,
44
+ "daemons": {
45
+ "/absolute/daemon/state-dir": {
46
+ "identities": ["BuildBot"]
47
+ }
48
+ }
49
+ }
52
50
  ```
53
51
 
54
- `voice-setup` presents a keyboard-driven single-choice provider selector and reads the
55
- provider key with hidden input. It writes config atomically with mode `0600`, preserves
56
- unrelated fields, and rolls back if the managed daemon cannot restart and report ready.
57
- It never accepts a key in command arguments. For environment-only service configuration
58
- use `OURS_STT_PROVIDER`, `OURS_STT_API_KEY`, `OURS_STT_MODEL`, `OURS_STT_BASE_URL`, and
59
- `OURS_STT_LANGUAGE`; environment values override file fields.
60
-
61
- The supported providers are `openai-compatible` (explicit base URL + model),
62
- `elevenlabs` (model), `deepgram`, and `custom` (`stt.custom.url`). Incoming voice remains
63
- a file and is transcribed only when its real `audio/*` MIME also carries
64
- `x-ours-kind=voice-message` (or its legacy filename starts `voice-message-`). The original
65
- bytes are saved whether transcription succeeds, is unconfigured, exceeds the size cap, or
66
- the provider fails. `get_files` preserves the human transcript/fallback line and also returns
67
- a secret-free structured outcome (`configured`, `attempted`, `status`, `provider`, `text`,
68
- `error_category`, `audio_path`, and `file_wire_id`). Provider error text is scrubbed if it
69
- echoes the configured key and raw provider diagnostics are not copied into structured output.
70
-
71
- Telegram fallback preserves its original OGG/Opus bytes and `.ogg` filename and advertises
72
- `audio/ogg; x-ours-kind=voice-message`; the connector's v2 message envelope correlates the
73
- separate file using `attachment.wire_id`.
74
-
75
- ## Daemon lifecycle
76
-
77
- This package is the **single owner of the daemon lifecycle**. `ours-mcp start`
78
- runs one long-lived HTTP daemon per host (default port 3050) that hosts every
79
- identity's packet, the broker socket, and file locks — a shared singleton that
80
- cannot be run per session. Each session instead runs a thin `ours-mcp proxy`
81
- (stdio ⇄ the daemon's HTTP endpoint), which auto-starts the daemon if it is down.
82
- Platform plugins ship only the proxy invocation; they never own or restart the
83
- daemon.
84
-
85
- `ours-mcp start` and `ours-mcp restart` do not treat an open socket as readiness.
86
- They wait for an authenticated response from the daemon's normal control
87
- surface (`/identities`) after the protocol runtime, contact-book registrar,
88
- persisted identities, and boot reconciliation are complete. In owner mode the
89
- CLI dynamically discovers the mode-`0600` token minted by the daemon before it
90
- declares readiness; shared and open modes preserve their configured auth
91
- semantics. During that wait the daemon publishes a mode-`0600`
92
- `startup-progress.json` in its state directory. The structured record contains
93
- only a phase, timestamps, process/boot identifiers, and identity counts — never
94
- identity names, container IDs, keys, packet contents, or state paths.
95
-
96
- Interactive terminals update one progress line; redirected/noninteractive runs
97
- emit concise stable lines such as `startup: Restoring identities 3/12`. A
98
- heartbeat distinguishes active work from a frozen process: 30 seconds without
99
- an update is a failure, and an absolute three-minute bound prevents an
100
- event-loop-active stall from waiting forever. Immediate daemon failure remains
101
- nonzero. The same daemon bootstrap/reporting path is used by foreground
102
- `serve`, Linux systemd, macOS launchd, and either native or WASM-backed ADAPT
103
- runtimes; service managers keep their existing lifecycle behavior.
104
-
105
- On connect, the proxy runs a compatibility handshake against the daemon's
106
- `/state-dir` report (`{ version, compat }`). `compat` is the wire-contract
107
- version (`src/protocol.ts`) — distinct from the package version, bumped only on
108
- breaking proxy↔daemon changes. Matching `compat` proceeds; a differing package
109
- version warns (stderr); an incompatible `compat` refuses with guidance to run
110
- `ours-mcp stop`. The proxy never kills the shared daemon itself, since it may
111
- be hosting other sessions' identities.
112
-
113
- ## Build
52
+ This list is application bookkeeping, not authorization. `choose_identity`
53
+ remains able to select any daemon identity and adopts it on success. Vanished
54
+ names remain recorded but are not rendered; idempotent close/remove cleans them.
55
+
56
+ ## Compatibility CLI
57
+
58
+ Former ours-mcp lifecycle entry points remain compatibility aliases that
59
+ delegate argv, stdio, and exit status to `ours daemon`. The `ours` executable
60
+ must be on `PATH`, or its exact path may be provided through `OURS_CLI`. No
61
+ command falls back to an embedded daemon.
62
+
63
+ `ours-mcp watch [identity]` streams inbound JSON Lines. With no identity argument,
64
+ only live identities in the selected daemon's ours-mcp application list are
65
+ watched.
66
+
67
+ ## Development
114
68
 
115
69
  ```sh
116
- npm run build # esbuild → minified dist/{index,cli}.js + dist/mufl_code/*.muflo
117
- npm run build:dev # readable build (unminified, intact stack traces)
118
- npm run typecheck
119
- npm run dev # run the daemon under tsx
70
+ npm install
71
+ npm run build --workspace @ours.network/mcp
72
+ npm run typecheck --workspace @ours.network/mcp
73
+ npm test --workspace @ours.network/mcp
120
74
  ```
121
-
122
- See the [repo README](https://github.com/adapt-toolkit/ours-mcp#readme) for install
123
- and quickstart.
@@ -0,0 +1,2 @@
1
+ import{randomUUID as g}from"node:crypto";import{chmod as l,mkdir as u,readFile as w,rename as h,unlink as I,writeFile as v}from"node:fs/promises";import{homedir as E}from"node:os";import{dirname as A,resolve as s}from"node:path";var m=1,N="OURS_MCP_CONFIG",P=()=>({version:1,daemons:{}});function S(e=process.env){let t=(e[N]??"").trim();return s(t||s(E(),".ours-mcp","config.json"))}function C(e,t){let i;try{i=JSON.parse(e)}catch(o){throw new Error(`Invalid ours-mcp application identity config at ${t}: ${String(o)}`)}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!==m)throw new Error(`Unsupported ours-mcp application identity config version at ${t}: expected ${m}, found ${String(n.version)}.`);if(!n.daemons||typeof n.daemons!="object"||Array.isArray(n.daemons))throw new Error(`Invalid ours-mcp application identity config at ${t}: "daemons" must be an object.`);let r={};for(let[o,a]of Object.entries(n.daemons)){if(!a||typeof a!="object"||Array.isArray(a))throw new Error(`Invalid ours-mcp application identity config at ${t}: daemon ${JSON.stringify(o)} must be an object.`);let c=a;if(!Array.isArray(c.identities)||c.identities.some(p=>typeof p!="string"||p.length===0))throw new Error(`Invalid ours-mcp application identity config at ${t}: daemon ${JSON.stringify(o)} has an invalid identity list.`);r[s(o)]={identities:[...new Set(c.identities)].sort()}}return{version:1,daemons:r}}async function d(e){try{return C(await w(e,"utf8"),e)}catch(t){if(t.code==="ENOENT")return P();throw t}}async function f(e,t){let i=A(e);await u(i,{recursive:!0,mode:448});let n=`${e}.${process.pid}.${g()}.tmp`;try{await v(n,`${JSON.stringify(t,null,2)}
2
+ `,{mode:384,flag:"wx"}),await h(n,e),await l(e,384)}catch(r){try{await I(n)}catch{}throw r}}var y=class{stateDir;path;constructor(t,i={}){this.stateDir=s(t),this.path=s(i.path??S(i.env))}async list(){return[...(await d(this.path)).daemons[this.stateDir]?.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=i.daemons[this.stateDir]?.identities??[];n.includes(t)||(i.daemons[this.stateDir]={identities:[...n,t].sort()},await f(this.path,i))}async remove(t){let i=await d(this.path),n=i.daemons[this.stateDir];if(!n?.identities.includes(t))return;let r=n.identities.filter(o=>o!==t);r.length===0?delete i.daemons[this.stateDir]:i.daemons[this.stateDir]={identities:r},await f(this.path,i)}};async function b(e,t){let i=new Set(await e.list());return t.filter(n=>i.has(n.name))}export{N as APPLICATION_IDENTITIES_ENV,m as APPLICATION_IDENTITIES_VERSION,y as ApplicationIdentityStore,S as applicationIdentityConfigPath,b as filterApplicationIdentities};