@brightbeamai/chap-coordinator-mcp 0.3.4 → 0.3.5

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
@@ -9,33 +9,54 @@ Spec target: **MCP 2026-07-28**, serving MCP 2025-11-25 clients as well · CHAP
9
9
 
10
10
  ## Run it as a server
11
11
 
12
- The package ships a stdio server, so an MCP client can use CHAP without any
13
- code. Point the client at it:
12
+ The package ships a stdio server, `chap-mcp-server`, so an MCP client can use
13
+ CHAP without any code. It makes every call as one participant, by default the
14
+ agent `agent:assistant`, as a create-chap-app mcp-gate project names its
15
+ agent, and offers the tools an agent's own work needs.
16
+ A person decides in a desk, never through the assistant, so the server
17
+ refuses to act as a `human:` participant and refuses a call that names
18
+ anyone else.
19
+
20
+ The usual setup has a CHAP server with a desk, such as a project made with
21
+ `npx create-chap-app my-gate --template mcp-gate`, and points the stdio server
22
+ at that server's JSON-RPC endpoint. The assistant's work then waits for the
23
+ person in the desk:
14
24
 
15
25
  ```json
16
26
  {
17
27
  "mcpServers": {
18
28
  "chap": {
19
29
  "command": "npx",
20
- "args": ["-y", "@brightbeamai/chap-coordinator-mcp"],
21
- "env": { "CHAP_DB_PATH": "~/chap.db" }
30
+ "args": ["-y", "@brightbeamai/chap-coordinator-mcp",
31
+ "--url", "http://127.0.0.1:8792/chap", "--as", "agent:assistant"]
22
32
  }
23
33
  }
24
34
  }
25
35
  ```
26
36
 
27
- All 39 methods arrive as tools named `chap.<method>`. Two environment
28
- variables configure it:
37
+ | Option | Environment | Effect |
38
+ |--------|-------------|--------|
39
+ | `--as <uri>` | `CHAP_PARTICIPANT` | The participant every call is made as. Default `agent:assistant`. `from` is filled in, a call naming anyone else is refused, and a `human:` URI is refused at start. |
40
+ | `--tools agent\|all` | | `agent`, the default, offers `AGENT_MCP_TOOLS` (below). `all` offers every method, still as the one participant. |
41
+ | `--url <endpoint>` | | Forward every call to a running CHAP server's JSON-RPC endpoint, such as a desk server's `http://127.0.0.1:8792/chap`. That server keeps the workspaces, its store and its profiles. A call waits 30 seconds for its answer. |
42
+ | `--db <path>` | `CHAP_DB_PATH` | Keep the workspaces in this SQLite file. A leading `~` is the home directory. |
43
+ | `--profiles <list>` | `CHAP_PROFILES` | Comma-separated profiles for new workspaces. Defaults to Core plus review, whisper, deliberation, handoff, control, routing, modes and audit-scitt, so the audit chain is on from the first entry. |
44
+ | `--help` | | Print the options and exit. |
45
+
46
+ An option takes its value as `--as agent:x` or `--as=agent:x`, and wins over
47
+ its environment variable; an empty variable counts as unset.
29
48
 
30
- | Variable | Effect |
31
- |----------|--------|
32
- | `CHAP_DB_PATH` | Path to a SQLite file. Without it the coordinator runs in memory and every workspace is lost when the client restarts. |
33
- | `CHAP_PROFILES` | Comma-separated profiles for new workspaces. Defaults to Core plus review, whisper, deliberation, handoff, control, routing, modes and audit-scitt, so the audit chain is on from the first entry. |
49
+ Without `--url` the server runs its own coordinator. With neither `--url` nor
50
+ `--db` it keeps everything in memory, says so on stderr, and every workspace
51
+ is lost when the client restarts. The agent tools create no workspace and
52
+ join no one, so a coordinator of its own suits a store that already holds the
53
+ workspace, or `--tools all`, where the assistant creates a workspace and
54
+ joins it as itself.
34
55
 
35
56
  Persistence needs `better-sqlite3`, an optional native dependency of the
36
- coordinator. If `CHAP_DB_PATH` is set and it cannot be loaded the server says
37
- so and exits rather than starting in memory and discarding the decisions it
38
- was asked to keep.
57
+ coordinator. If `--db` or `CHAP_DB_PATH` is set and it cannot be loaded the
58
+ server says so and exits, so it never starts in memory and discards the
59
+ decisions it was asked to keep.
39
60
 
40
61
  The server is also listed in the [MCP registry](https://registry.modelcontextprotocol.io)
41
62
  as `io.github.BrightbeamAI/chap`.
@@ -46,21 +67,25 @@ as `io.github.BrightbeamAI/chap`.
46
67
  npm install @brightbeamai/chap-coordinator-mcp
47
68
  ```
48
69
 
49
- Node 20+ required. Runtime dependencies: `@brightbeamai/chap-coordinator`,
70
+ Node 22+ required. Runtime dependencies: `@brightbeamai/chap-coordinator`,
50
71
  `@modelcontextprotocol/sdk` and `zod`. The binary is `chap-mcp-server`.
51
72
 
52
73
  ## Quick start
53
74
 
54
75
  ```typescript
55
76
  import { Coordinator } from "@brightbeamai/chap-coordinator";
56
- import { makeChapMcpServer } from "@brightbeamai/chap-coordinator-mcp";
77
+ import { AGENT_MCP_TOOLS, makeChapMcpServer } from "@brightbeamai/chap-coordinator-mcp";
57
78
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
58
79
 
59
80
  const coord = new Coordinator({
60
81
  defaultProfiles: ["core/1.0", "review/1.0", "audit-scitt/1.0"],
61
82
  });
62
83
 
63
- const server = makeChapMcpServer(coord, { name: "chap", version: "0.3.4" });
84
+ // The assistant's own connection: every call as the agent, with its tools.
85
+ const server = makeChapMcpServer(coord, {
86
+ participant: "agent:assistant",
87
+ toolFilter: (name) => AGENT_MCP_TOOLS.has(name),
88
+ });
64
89
  await server.connect(new StdioServerTransport());
65
90
  ```
66
91
 
@@ -68,14 +93,130 @@ The returned `Server` is the official MCP SDK's `Server` class with
68
93
  `tools/list` and `tools/call` handlers pre-registered. Attach any MCP
69
94
  transport (stdio, Streamable HTTP, etc.).
70
95
 
96
+ `makeChapMcpServer(coord, options)` takes:
97
+
98
+ | Option | Effect |
99
+ |--------|--------|
100
+ | `participant` | Make every call as this participant: `from` is filled in, and a call that names another `from` is refused. The tools list `from` as optional, with this URI in its description. A `human:` URI is refused, as the commands refuse it. |
101
+ | `toolFilter` | `(toolName) => boolean`. A tool the filter excludes is left out of the list and refused when called by name. |
102
+ | `name`, `version` | What the server reports to its client. |
103
+ | `envelopeIdFactory` | The id of each envelope a tool call sends. |
104
+
105
+ Without `participant` or `toolFilter`,
106
+ `makeChapMcpServer(coord, { name: "chap", version: "0.3.5" })` serves every
107
+ tool and each call names its own `from`. That suits a connection that stands
108
+ for no one member, such as a service of your own that authenticates its
109
+ callers and checks the `from` they send.
110
+
111
+ `coord` is a `Coordinator`, or any object with the same
112
+ `dispatch(envelope)`, returning the response or a promise of it.
113
+
114
+ `AGENT_MCP_TOOLS` is the agent's set: `chap.task.create`, `chap.task.update`,
115
+ `chap.task.complete`, `chap.whisper.ask`, `chap.decide.approve`,
116
+ `chap.decide.reject`, `chap.decide.override`, `chap.review.acknowledge`,
117
+ `chap.workspace.describe`, `chap.audit.read` and `chap.audit.verify_chain`.
118
+ The decision tools and `chap.review.acknowledge` stay for work the agent is
119
+ asked to review; on its own work the coordinator refuses them with `-32011`
120
+ and records the attempt. It leaves out the tools that choose who reviews the
121
+ agent's work or pass its review on: `chap.review.request`, whose `to` can
122
+ name the agent itself, `chap.escalate.raise`, which passes the open review
123
+ on to a successor task, and every workspace, membership and control method.
124
+ `chap.task.update` stays, so the agent can take a task under review back to
125
+ `in_progress`, which withdraws its draft; its next completion opens the
126
+ review again, so a task that holds a review completes only through a
127
+ decision. A task with no review open completes when the agent completes it,
128
+ so set `review_required` on any task the agent must not finish alone.
129
+
71
130
  A runnable reference server is at
72
131
  [`reference/mcp-server-ts/`](../../reference/mcp-server-ts/).
73
132
 
133
+ ## Serve it over HTTP
134
+
135
+ `chapMcpHttpHandler` answers MCP on an HTTP endpoint such as `/mcp`, for a
136
+ Node `http` request and response, in both protocol eras:
137
+
138
+ ```typescript
139
+ function chapMcpHttpHandler(
140
+ coord: ChapDispatcher,
141
+ options?: ChapMcpHttpOptions,
142
+ ): (req: IncomingMessage, res: ServerResponse, parsedBody?: unknown) => Promise<void>;
143
+
144
+ interface ChapMcpHttpOptions extends ChapMcpOptions {
145
+ maxBodyBytes?: number; // default 1,114,112: 1 MiB, and 64 KiB of MCP around it
146
+ allowedHosts?: string[]; // Host values served besides the loopback names, such as "mcp.internal:8792"
147
+ allowedOrigins?: string[]; // origins a browser page may call from, such as "http://localhost:6274"
148
+ timeoutMs?: number; // default 45,000
149
+ }
150
+ ```
151
+
152
+ `options` are those of `makeChapMcpServer`, so `participant` and
153
+ `toolFilter` hold over HTTP as on stdio. Pass `parsedBody` when your server
154
+ has read and parsed the body already; otherwise the handler reads it, up to
155
+ `maxBodyBytes`.
156
+
157
+ ```typescript
158
+ import { createServer } from "node:http";
159
+ import { AGENT_MCP_TOOLS, chapMcpHttpHandler } from "@brightbeamai/chap-coordinator-mcp";
160
+
161
+ const mcp = chapMcpHttpHandler(coord, {
162
+ participant: "agent:assistant",
163
+ toolFilter: (name) => AGENT_MCP_TOOLS.has(name),
164
+ });
165
+ createServer((req, res) => {
166
+ if (new URL(req.url ?? "/", "http://localhost").pathname === "/mcp") return mcp(req, res);
167
+ res.writeHead(404).end();
168
+ }).listen(8792, "127.0.0.1");
169
+ ```
170
+
171
+ The handler answers under the server's own names only, as the MCP
172
+ specification requires against DNS rebinding: a `Host` other than
173
+ 127.0.0.1, localhost or [::1] on the port the request came in on is refused
174
+ with 421, and a browser's request from another origin with 403. Name the
175
+ host a proxy passes in `allowedHosts`, and the origin of a page that calls
176
+ the server, such as the MCP Inspector's, in `allowedOrigins`; such a page
177
+ also needs the CORS headers, which the server the handler is mounted in
178
+ sends. Anything but POST is refused with 405, in both eras.
179
+
180
+ A request with no `MCP-Protocol-Version` header, or a handshake-era version
181
+ (2025-11-25 and earlier), goes to the SDK's Streamable HTTP transport, with
182
+ a server and a transport for each request and no session. A batch is
183
+ refused with 400, as every revision since 2025-06-18 refuses one, and so is
184
+ a body whose envelope declares a protocol version the header does not name
185
+ (`-32020`). Any other version takes the 2026-07-28 binding: one POST
186
+ carrying one JSON-RPC request, answered with one JSON object. Its headers
187
+ are checked against its body, and an error carries that binding's status:
188
+ 400 for a malformed request, a mismatched header (`-32020`) or an
189
+ unsupported version (`-32022`), 404 for a method it does not serve. The
190
+ SDK's transport alone refuses every 2026-07-28 request with 400.
191
+
192
+ A request with no answer within `timeoutMs` is answered with an internal
193
+ error, `-32603`, and one whose client has gone stops waiting at once.
194
+
195
+ ## What an unsigned local server trusts
196
+
197
+ MCP calls carry no signature. `participant` keeps a connection to one name,
198
+ and that is all it does: the stdio server trusts the process that started it,
199
+ so whatever the client sends is made as the participant. The log records
200
+ that the call came through this connection; it does not show that the
201
+ participant signed it. Anything that can start the command, or reach a
202
+ server's MCP endpoint, acts as that participant.
203
+
204
+ With `--url`, the server at the other end applies its own rules to each call.
205
+ One that requires a signature on every call refuses these with `-32070`; one
206
+ that requires signatures from people only accepts the agent's unsigned calls.
207
+ The pin covers this connection only. A CHAP server without signatures
208
+ accepts a call in any member's name from any local process, so the record of
209
+ a person's decision holds only where the server requires the desk's
210
+ signature on it.
211
+
74
212
  ## What gets exposed
75
213
 
76
- All 39 CHAP methods become MCP tools named `chap.<method>`. Each
214
+ Every CHAP method becomes an MCP tool named `chap.<method>`. Each
77
215
  tool's `inputSchema` is the JSON Schema for the corresponding method's
78
- params. Tool descriptions are tuned for LLM consumption.
216
+ params. Tool descriptions are tuned for LLM consumption. A call naming no
217
+ CHAP tool is a protocol error, `-32602` "Unknown tool: <name>", as the MCP
218
+ tools specification has it; a CHAP tool the server leaves out is refused
219
+ as a tool result, as the coordinator's refusals are.
79
220
 
80
221
  The `chap.` prefix avoids collisions with other MCP servers a client
81
222
  might have loaded simultaneously.
@@ -103,7 +244,9 @@ npm test
103
244
 
104
245
  The integration tests use the SDK's `InMemoryTransport.createLinkedPair()`
105
246
  to drive a wrapped Coordinator end-to-end, exercising every major
106
- profile.
247
+ profile. `cli.test.ts` starts `chap-mcp-server` the way an MCP client does,
248
+ with and without `--url`, and `http.test.ts` serves both protocol eras over
249
+ HTTP.
107
250
 
108
251
  ## See also
109
252