@multiagentcoordinationprotocol/mcp 0.2.0 → 0.3.0
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/CHANGELOG.md +7 -0
- package/README.md +106 -22
- package/contract/gate/cli.json +32 -0
- package/contract/gate/context-id-vectors.json +164 -0
- package/contract/gate/example.gate.json +22 -0
- package/contract/gate/gate.schema.json +70 -0
- package/contract/gate/session.json +82 -0
- package/dist/bridge.d.ts +3 -1
- package/dist/bridge.js +2 -1
- package/dist/cli.d.ts +5 -1
- package/dist/cli.js +13 -1
- package/dist/gate/approval.d.ts +66 -0
- package/dist/gate/approval.js +202 -0
- package/dist/gate/cli.d.ts +14 -0
- package/dist/gate/cli.js +71 -0
- package/dist/gate/config.d.ts +38 -0
- package/dist/gate/config.js +111 -0
- package/dist/gate/context-id.d.ts +9 -0
- package/dist/gate/context-id.js +22 -0
- package/dist/gate/contract.d.ts +19 -0
- package/dist/gate/contract.js +64 -0
- package/dist/gate/jcs.d.ts +18 -0
- package/dist/gate/jcs.js +56 -0
- package/dist/gate/proxy.d.ts +30 -0
- package/dist/gate/proxy.js +71 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +34 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0](https://github.com/multiagentcoordinationprotocol/macp-mcp-typescript/compare/v0.2.0...v0.3.0) (2026-10-25)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **gate:** macp-mcp gate — quorum-gated upstream tool calls (W3.8) ([#5](https://github.com/multiagentcoordinationprotocol/macp-mcp-typescript/issues/5)) ([503f5f4](https://github.com/multiagentcoordinationprotocol/macp-mcp-typescript/commit/503f5f413ee77de1d95d9e7802f647313d6e1df7))
|
|
9
|
+
|
|
3
10
|
## [0.2.0](https://github.com/multiagentcoordinationprotocol/macp-mcp-typescript/compare/v0.1.0...v0.2.0) (2026-10-25)
|
|
4
11
|
|
|
5
12
|
|
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ session. It is the TypeScript twin of the Python
|
|
|
6
6
|
[macp-mcp](https://github.com/multiagentcoordinationprotocol/macp-mcp). Both
|
|
7
7
|
expose the same tool surface: the same names, input schemas and outputs,
|
|
8
8
|
pinned by one shared manifest ([Tool contract](#tool-contract)).
|
|
9
|
+
Package versions are independent of the Python macp-mcp; tool parity is
|
|
10
|
+
enforced by `make verify-contract`, not by matching version numbers.
|
|
9
11
|
|
|
10
12
|
MCP decides which tools a model can call. MACP decides who may use them and
|
|
11
13
|
when, and records what was decided. The model drives every step through
|
|
@@ -69,8 +71,8 @@ claude mcp add macp \
|
|
|
69
71
|
-- npx -y @multiagentcoordinationprotocol/mcp
|
|
70
72
|
```
|
|
71
73
|
|
|
72
|
-
Then ask the model something like:
|
|
73
|
-
which deployment plan to use, propose a canary rollout, and vote for it."
|
|
74
|
+
Then ask the model something like: _"Start a MACP decision with alice on
|
|
75
|
+
which deployment plan to use, propose a canary rollout, and vote for it."_
|
|
74
76
|
|
|
75
77
|
### Against a production runtime
|
|
76
78
|
|
|
@@ -89,31 +91,31 @@ this agent:
|
|
|
89
91
|
|
|
90
92
|
These are the same variables, with the same defaults, as the Python server.
|
|
91
93
|
|
|
92
|
-
| Variable
|
|
93
|
-
|
|
94
|
-
| `MACP_RUNTIME_TARGET` | `127.0.0.1:50051` | Runtime gRPC address (`host:port`).
|
|
95
|
-
| `MACP_AGENT_ID`
|
|
96
|
-
| `MACP_AUTH_TOKEN`
|
|
97
|
-
| `MACP_ALLOW_INSECURE` | off
|
|
98
|
-
| `MACP_TLS_CA_FILE`
|
|
99
|
-
| `MACP_DEFAULT_TTL_MS` | `300000`
|
|
100
|
-
| `MACP_REPLAY_IDLE_MS` | `1000`
|
|
94
|
+
| Variable | Default | Meaning |
|
|
95
|
+
| --------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
96
|
+
| `MACP_RUNTIME_TARGET` | `127.0.0.1:50051` | Runtime gRPC address (`host:port`). |
|
|
97
|
+
| `MACP_AGENT_ID` | `mcp-agent` | The MACP agent this server acts as. With dev-agent auth it is also the bearer token. With `MACP_AUTH_TOKEN` it is the identity the runtime must bind the token to. |
|
|
98
|
+
| `MACP_AUTH_TOKEN` | unset | Bearer token issued by the runtime. When unset, dev-agent auth is used, which needs a runtime started with `MACP_ALLOW_INSECURE=1`. |
|
|
99
|
+
| `MACP_ALLOW_INSECURE` | off | `1`/`true`/`yes`/`on` uses plaintext gRPC. For a local runtime only. |
|
|
100
|
+
| `MACP_TLS_CA_FILE` | system trust | PEM bundle for a private CA. Cannot be combined with `MACP_ALLOW_INSECURE`. |
|
|
101
|
+
| `MACP_DEFAULT_TTL_MS` | `300000` | Session TTL used when `start_session` is called without `ttl_ms`. |
|
|
102
|
+
| `MACP_REPLAY_IDLE_MS` | `1000` | How long a history read waits for the next envelope before treating the replay as complete. |
|
|
101
103
|
|
|
102
104
|
The server logs to stderr only, because stdout carries the MCP protocol. A bad
|
|
103
105
|
configuration exits with status 2 and a one-line message.
|
|
104
106
|
|
|
105
107
|
## Tools
|
|
106
108
|
|
|
107
|
-
| Tool
|
|
108
|
-
|
|
109
|
-
| `start_session`
|
|
110
|
-
| `propose`
|
|
111
|
-
| `evaluate`
|
|
112
|
-
| `vote`
|
|
113
|
-
| `commit`
|
|
114
|
-
| `get_session`
|
|
115
|
-
| `get_commitment` | read
|
|
116
|
-
| `verify`
|
|
109
|
+
| Tool | Kind | What it does |
|
|
110
|
+
| ---------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
111
|
+
| `start_session` | write | Start a Decision session with this agent as initiator. Takes `intent`, `participants` and optional `ttl_ms` and `context_id`. Returns the `session_id` to share. |
|
|
112
|
+
| `propose` | write | Put an option (`proposal_id`, `option`, `rationale`) up for decision. |
|
|
113
|
+
| `evaluate` | write | Record a scored assessment (`APPROVE` / `REVIEW` / `BLOCK` / `REJECT`, confidence 0..1) before voting. |
|
|
114
|
+
| `vote` | write | Cast this agent's `APPROVE` / `REJECT` / `ABSTAIN` vote on a proposal. |
|
|
115
|
+
| `commit` | write | Resolve the session with a binding Commitment (`action`, `authority_scope`, `reason`). Under the default policy only the initiator can commit. |
|
|
116
|
+
| `get_session` | read | The session's state, participants, proposals, evaluations, votes, approve totals and majority winner. |
|
|
117
|
+
| `get_commitment` | read | The resolved session's binding Commitment, the exact payload bytes (`payload_b64`) and its RFC-MACP-0013 `commitment_hash`. |
|
|
118
|
+
| `verify` | read | Recompute a commitment hash with the SDK verifier, either from the runtime's recorded history (`session_id`) or from bytes you hold (`payload_b64`). Returns `match`, `mismatch` or `cannot_verify`. |
|
|
117
119
|
|
|
118
120
|
Errors the model can act on, such as a runtime rejection or a bad argument,
|
|
119
121
|
come back as tool errors with the text `"<CODE>: <message>"`, for example
|
|
@@ -124,6 +126,87 @@ the gRPC status name.
|
|
|
124
126
|
The server is stateless. Every read replays the session from the runtime, so a
|
|
125
127
|
restart loses nothing, and several servers (agents) can share one session.
|
|
126
128
|
|
|
129
|
+
## Gate upstream tool calls (`macp-mcp gate`)
|
|
130
|
+
|
|
131
|
+
`macp-mcp gate` is a second mode of the same binary. It runs as an MCP server
|
|
132
|
+
in front of another (upstream) MCP server, and puts the tool calls you name
|
|
133
|
+
behind a MACP session: a matching call reaches the upstream only after a
|
|
134
|
+
Quorum or Decision session commits `approve`. Every other call passes straight
|
|
135
|
+
through.
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
macp-mcp gate --policy gate.json --upstream npx -y @modelcontextprotocol/server-filesystem /srv/data
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`--upstream` takes the rest of the command line as the upstream's stdio
|
|
142
|
+
command. The gate uses the same environment variables as the base server
|
|
143
|
+
(`MACP_AGENT_ID` is the gate's identity: it initiates and commits every
|
|
144
|
+
session). The upstream inherits the gate's environment minus
|
|
145
|
+
`MACP_AUTH_TOKEN`, so the gate's credential never reaches it.
|
|
146
|
+
|
|
147
|
+
`gate.json` names the rules; the first rule whose `tool` matches wins (exact
|
|
148
|
+
name, or a glob with `*` and `?`):
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"version": 1,
|
|
153
|
+
"server": "filesystem",
|
|
154
|
+
"rules": [
|
|
155
|
+
{ "tool": "write_*", "mode": "macp.mode.quorum.v1", "participants": ["alice", "bob", "carol"], "threshold": 2 },
|
|
156
|
+
{ "tool": "move_file", "mode": "macp.mode.decision.v1", "participants": ["alice", "bob"], "ttl_ms": 60000 }
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
| Field | Meaning |
|
|
162
|
+
| ---------------- | ------------------------------------------------------------------------------------------------- |
|
|
163
|
+
| `server` | The upstream's name, bound into every gated call's `context_id`. |
|
|
164
|
+
| `tool` | Exact tool name or glob. |
|
|
165
|
+
| `mode` | `macp.mode.quorum.v1` (ApprovalRequest + ballots) or `macp.mode.decision.v1` (Proposal + votes). |
|
|
166
|
+
| `participants` | The approvers. Must not include the gate's own agent id. |
|
|
167
|
+
| `threshold` | APPROVE ballots needed, in either mode. Default: a strict majority of `participants`. |
|
|
168
|
+
| `ttl_ms` | Session TTL. Default `300000`. |
|
|
169
|
+
| `policy_version` | Optional RFC-MACP-0012 policy to bind the session to, for example one of the spec's policy packs. |
|
|
170
|
+
|
|
171
|
+
For a gated call the gate:
|
|
172
|
+
|
|
173
|
+
1. Starts a session whose `context_id` is `sha256:` + the hex SHA-256 of the
|
|
174
|
+
RFC 8785 (JCS) form of `{"arguments", "server", "tool"}`. The same canonical
|
|
175
|
+
JSON travels in the ApprovalRequest `details` (Quorum) or the Proposal
|
|
176
|
+
`rationale` (Decision), so an approver can recompute the binding.
|
|
177
|
+
2. Commits `approve` as soon as the approvers' latest ballots reach the
|
|
178
|
+
threshold, or `deny` as soon as they no longer can (REJECT and ABSTAIN both
|
|
179
|
+
count as not-approve).
|
|
180
|
+
3. On `approve`, forwards the call and adds the commitment to the result,
|
|
181
|
+
beside any `_meta` the upstream set:
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
"_meta": {
|
|
185
|
+
"io.multiagentcoordinationprotocol/commitment": {
|
|
186
|
+
"session_id": "…", "mode": "macp.mode.quorum.v1", "outcome": "approve",
|
|
187
|
+
"commitment_hash": "sha256:…"
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`commitment_hash` is the RFC-MACP-0013 hash; the base server's `verify`
|
|
193
|
+
tool recomputes it from the runtime's history.
|
|
194
|
+
|
|
195
|
+
4. Otherwise refuses with a tool error, and never calls the upstream:
|
|
196
|
+
`POLICY_DENIED: …` when the session commits `deny` (or a bound policy
|
|
197
|
+
refuses the commitment), `SESSION_NOT_OPEN: …` when the session expires or
|
|
198
|
+
is cancelled without a commitment. If the session is still open 5 s after
|
|
199
|
+
its TTL, the gate cancels it. A call whose arguments hold an integer
|
|
200
|
+
outside ±(2^53−1) is refused with `INVALID_ARGUMENT: …` before any session
|
|
201
|
+
opens: JSON may already have rounded it, so it has no canonical form.
|
|
202
|
+
|
|
203
|
+
Approvers are ordinary MACP agents, for example a base `macp-mcp` server
|
|
204
|
+
driven by a person or another model, voting through the runtime. See
|
|
205
|
+
[examples/gated-tool-call.md](examples/gated-tool-call.md) for a full
|
|
206
|
+
transcript. The policy schema, CLI flags, wire values and `context_id` test
|
|
207
|
+
vectors live in [`contract/gate/`](contract/gate), byte-identical to the
|
|
208
|
+
Python macp-mcp's.
|
|
209
|
+
|
|
127
210
|
## Tool contract
|
|
128
211
|
|
|
129
212
|
[`contract/tools.json`](contract/tools.json) pins the tool names,
|
|
@@ -135,7 +218,8 @@ arguments against it with Ajv, so the advertised surface cannot drift from
|
|
|
135
218
|
the file. The file also ships in the npm package as
|
|
136
219
|
`@multiagentcoordinationprotocol/mcp/contract/tools.json`.
|
|
137
220
|
|
|
138
|
-
|
|
221
|
+
The gate's contract (`contract/gate/`) is vendored the same way. CI fails if
|
|
222
|
+
any of these files drifts from the Python repo's (`make verify-contract`).
|
|
139
223
|
After a deliberate surface change lands in macp-mcp, run `make sync-contract`
|
|
140
224
|
and implement the change here.
|
|
141
225
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contract_version": "1",
|
|
3
|
+
"command": "macp-mcp gate",
|
|
4
|
+
"usage": "macp-mcp gate --policy <gate.json> --upstream <command> [args...]",
|
|
5
|
+
"flags": [
|
|
6
|
+
{
|
|
7
|
+
"name": "--policy",
|
|
8
|
+
"required": true,
|
|
9
|
+
"value": "PATH",
|
|
10
|
+
"description": "The gate.json policy file (contract/gate/gate.schema.json)."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "--upstream",
|
|
14
|
+
"required": true,
|
|
15
|
+
"value": "COMMAND [ARGS...]",
|
|
16
|
+
"description": "The upstream MCP server's stdio command line. Must be the last option: every argument after it belongs to the upstream command."
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"environment": {
|
|
20
|
+
"read": ["MACP_RUNTIME_TARGET", "MACP_AGENT_ID", "MACP_AUTH_TOKEN", "MACP_ALLOW_INSECURE", "MACP_TLS_CA_FILE"],
|
|
21
|
+
"withheld_from_upstream": ["MACP_AUTH_TOKEN"],
|
|
22
|
+
"note": "The upstream process inherits the gate's environment minus withheld_from_upstream. The gate is the MACP agent MACP_AGENT_ID; it initiates and commits every approval session."
|
|
23
|
+
},
|
|
24
|
+
"exit_codes": {
|
|
25
|
+
"2": "Unusable configuration: bad MACP_* environment, an invalid gate.json, or a missing --upstream command. One line on stderr."
|
|
26
|
+
},
|
|
27
|
+
"server": {
|
|
28
|
+
"name": "macp-mcp-gate",
|
|
29
|
+
"proxies": ["tools/list", "tools/call"],
|
|
30
|
+
"instructions": "Some tools on this server are gated by MACP: a call to one opens a MACP session and runs only after the named approvers approve it. A refused call returns an error starting POLICY_DENIED or SESSION_NOT_OPEN; do not retry it unchanged."
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contract_version": "1",
|
|
3
|
+
"description": "macp-mcp gate context_id vectors. context_id = \"sha256:\" + lowercase hex SHA-256 of the RFC 8785 (JCS) canonical JSON (UTF-8) of {\"arguments\": input.arguments or {}, \"server\": input.server, \"tool\": input.tool}. preimage is that canonical JSON. Numbers are IEEE-754 doubles, as JSON.parse reads them. A vector with expected_error instead of preimage and context_id is refused with exactly that text, before any session is opened.",
|
|
4
|
+
"vectors": [
|
|
5
|
+
{
|
|
6
|
+
"name": "arguments-absent",
|
|
7
|
+
"note": "Absent arguments hash as {}.",
|
|
8
|
+
"input": {
|
|
9
|
+
"server": "filesystem",
|
|
10
|
+
"tool": "list_allowed_directories"
|
|
11
|
+
},
|
|
12
|
+
"preimage": "{\"arguments\":{},\"server\":\"filesystem\",\"tool\":\"list_allowed_directories\"}",
|
|
13
|
+
"context_id": "sha256:d79291c4a1ee28e189486c939345523709718be643a11d098f2254eae7b3f63a"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "spec-worked-example",
|
|
17
|
+
"note": "The worked example in the spec repo's docs/mcp-tool-gating.md.",
|
|
18
|
+
"input": {
|
|
19
|
+
"server": "filesystem",
|
|
20
|
+
"tool": "write_file",
|
|
21
|
+
"arguments": {
|
|
22
|
+
"mode": "w",
|
|
23
|
+
"path": "/etc/hosts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"preimage": "{\"arguments\":{\"mode\":\"w\",\"path\":\"/etc/hosts\"},\"server\":\"filesystem\",\"tool\":\"write_file\"}",
|
|
27
|
+
"context_id": "sha256:e8272f181497f6286a735f2bd9a2bd535b65e377fc85b6ece9d3a6bac5b73057"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "simple",
|
|
31
|
+
"note": "Flat string arguments; keys sorted.",
|
|
32
|
+
"input": {
|
|
33
|
+
"server": "filesystem",
|
|
34
|
+
"tool": "write_file",
|
|
35
|
+
"arguments": {
|
|
36
|
+
"path": "/srv/reports/q3.md",
|
|
37
|
+
"content": "Q3 revenue: up 4%"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"preimage": "{\"arguments\":{\"content\":\"Q3 revenue: up 4%\",\"path\":\"/srv/reports/q3.md\"},\"server\":\"filesystem\",\"tool\":\"write_file\"}",
|
|
41
|
+
"context_id": "sha256:330ccb8df9d72d67b7d4835a591e313b5882bf79f69726d9fbfd06d896d77196"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "nested",
|
|
45
|
+
"note": "Nested objects and arrays, booleans and null; array order is kept.",
|
|
46
|
+
"input": {
|
|
47
|
+
"server": "github",
|
|
48
|
+
"tool": "create_issue",
|
|
49
|
+
"arguments": {
|
|
50
|
+
"repo": "acme/api",
|
|
51
|
+
"labels": [
|
|
52
|
+
"bug",
|
|
53
|
+
"p1"
|
|
54
|
+
],
|
|
55
|
+
"assignee": null,
|
|
56
|
+
"draft": false,
|
|
57
|
+
"body": {
|
|
58
|
+
"steps": [
|
|
59
|
+
3,
|
|
60
|
+
1,
|
|
61
|
+
2
|
|
62
|
+
],
|
|
63
|
+
"env": {
|
|
64
|
+
"os": "linux",
|
|
65
|
+
"arch": "arm64"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"preimage": "{\"arguments\":{\"assignee\":null,\"body\":{\"env\":{\"arch\":\"arm64\",\"os\":\"linux\"},\"steps\":[3,1,2]},\"draft\":false,\"labels\":[\"bug\",\"p1\"],\"repo\":\"acme/api\"},\"server\":\"github\",\"tool\":\"create_issue\"}",
|
|
71
|
+
"context_id": "sha256:d7f3bdf9be0c206ff4afa7b605f9f3e9b8386a850b76e2933a09f125dc8b1ce2"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "numbers",
|
|
75
|
+
"note": "ECMAScript number formatting: 1.0 -> 1, -0.0 -> 0, small exponents, shortest round-trip, the largest safe integer.",
|
|
76
|
+
"input": {
|
|
77
|
+
"server": "payments",
|
|
78
|
+
"tool": "transfer",
|
|
79
|
+
"arguments": {
|
|
80
|
+
"amount": 1250.5,
|
|
81
|
+
"fee": 0.1,
|
|
82
|
+
"whole": 1.0,
|
|
83
|
+
"negzero": -0.0,
|
|
84
|
+
"small": 1.5e-07,
|
|
85
|
+
"tiny": 1e-07,
|
|
86
|
+
"edge": 1e-06,
|
|
87
|
+
"thirds": 333333333.3333333,
|
|
88
|
+
"max_safe": 9007199254740991,
|
|
89
|
+
"neg": -42
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"preimage": "{\"arguments\":{\"amount\":1250.5,\"edge\":0.000001,\"fee\":0.1,\"max_safe\":9007199254740991,\"neg\":-42,\"negzero\":0,\"small\":1.5e-7,\"thirds\":333333333.3333333,\"tiny\":1e-7,\"whole\":1},\"server\":\"payments\",\"tool\":\"transfer\"}",
|
|
93
|
+
"context_id": "sha256:f5b6ab689bbd1d567b0729312865fc20a4666dc39644a65fdc41636740793482"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "strings-escapes",
|
|
97
|
+
"note": "Escapes: quote, backslash, \\b \\f \\n \\r \\t, other controls as \\u00XX; DEL, U+2028, U+2029, '/' and non-ASCII stay literal.",
|
|
98
|
+
"input": {
|
|
99
|
+
"server": "mail",
|
|
100
|
+
"tool": "send",
|
|
101
|
+
"arguments": {
|
|
102
|
+
"subject": "Re: \"Q3\" \\ final",
|
|
103
|
+
"body": "line1\nline2\ttab\u0007bell\u0001soh\b\f\rdel
ls
ps",
|
|
104
|
+
"url": "https://example.com/a/b",
|
|
105
|
+
"to": "zoë@example.com",
|
|
106
|
+
"greeting": "שלום 😀"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"preimage": "{\"arguments\":{\"body\":\"line1\\nline2\\ttab\\u0007bell\\u0001soh\\b\\f\\rdel
ls
ps\",\"greeting\":\"שלום 😀\",\"subject\":\"Re: \\\"Q3\\\" \\\\ final\",\"to\":\"zoë@example.com\",\"url\":\"https://example.com/a/b\"},\"server\":\"mail\",\"tool\":\"send\"}",
|
|
110
|
+
"context_id": "sha256:fe91dc79441555be100f7e181af5b940e071aeebb8d89eb6bac9127f71f812b9"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "key-order-utf16",
|
|
114
|
+
"note": "Keys sort by UTF-16 code units, not code points: U+20AC < U+1F600 (D83D) < U+FB01 < U+FB33.",
|
|
115
|
+
"input": {
|
|
116
|
+
"server": "kv",
|
|
117
|
+
"tool": "put",
|
|
118
|
+
"arguments": {
|
|
119
|
+
"דּ": 4,
|
|
120
|
+
"fi": 3,
|
|
121
|
+
"😀": 2,
|
|
122
|
+
"€": 1,
|
|
123
|
+
"ö": 0,
|
|
124
|
+
"": 0,
|
|
125
|
+
"b": 0,
|
|
126
|
+
"B": 0,
|
|
127
|
+
"_": 0,
|
|
128
|
+
"1": 0,
|
|
129
|
+
"\r": 0,
|
|
130
|
+
"": 0
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
"preimage": "{\"arguments\":{\"\":0,\"\\r\":0,\"1\":0,\"B\":0,\"_\":0,\"b\":0,\"\":0,\"ö\":0,\"€\":1,\"😀\":2,\"fi\":3,\"דּ\":4},\"server\":\"kv\",\"tool\":\"put\"}",
|
|
134
|
+
"context_id": "sha256:199c7c5bcee590b3c55b44e33c1fed2fcfac4b7d4e54e8f76b4ddb3d8d1ef355"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "unsafe-integer",
|
|
138
|
+
"note": "An integer outside ±(2^53−1) has no canonical form: JSON.parse may already have rounded it. The call is refused with expected_error; no session is opened.",
|
|
139
|
+
"input": {
|
|
140
|
+
"server": "ledger",
|
|
141
|
+
"tool": "post_entry",
|
|
142
|
+
"arguments": {
|
|
143
|
+
"entry_id": 9007199254740993
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"expected_error": "INVALID_ARGUMENT: tool call post_entry arguments contain an integer outside ±(2^53−1), which has no canonical JSON form"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"name": "unsafe-integer-exponent",
|
|
150
|
+
"note": "The same holds for an integral value written with an exponent (1e21), here nested in an array.",
|
|
151
|
+
"input": {
|
|
152
|
+
"server": "ledger",
|
|
153
|
+
"tool": "post_entry",
|
|
154
|
+
"arguments": {
|
|
155
|
+
"ids": [
|
|
156
|
+
1,
|
|
157
|
+
1e+21
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"expected_error": "INVALID_ARGUMENT: tool call post_entry arguments contain an integer outside ±(2^53−1), which has no canonical JSON form"
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"server": "filesystem",
|
|
4
|
+
"rules": [
|
|
5
|
+
{
|
|
6
|
+
"tool": "write_file",
|
|
7
|
+
"mode": "macp.mode.quorum.v1",
|
|
8
|
+
"participants": ["alice", "bob", "carol"],
|
|
9
|
+
"threshold": 2,
|
|
10
|
+
"ttl_ms": 120000,
|
|
11
|
+
"policy_version": "policy.packs.two-agent-rule",
|
|
12
|
+
"description": "Four eyes on every write: two of three reviewers approve."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"tool": "move_*",
|
|
16
|
+
"mode": "macp.mode.decision.v1",
|
|
17
|
+
"participants": ["alice", "bob"],
|
|
18
|
+
"ttl_ms": 60000,
|
|
19
|
+
"description": "Moves and renames need a majority vote (2 of 2)."
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://multiagentcoordinationprotocol.io/schemas/macp-mcp/gate.schema.json",
|
|
4
|
+
"title": "macp-mcp gate policy (gate.json)",
|
|
5
|
+
"description": "Which tool calls on one upstream MCP server need a MACP session, and how that session is run. Rules are tried in order; the first whose tool pattern matches decides. A call that matches no rule is forwarded to the upstream untouched. Loaders also enforce two rules a schema cannot: threshold <= the rule's participant count, and participants never contains the gate's own MACP_AGENT_ID.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["version", "server", "rules"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"version": {
|
|
11
|
+
"description": "gate.json format version.",
|
|
12
|
+
"const": 1
|
|
13
|
+
},
|
|
14
|
+
"server": {
|
|
15
|
+
"description": "The upstream's configured name. It is the \"server\" member of the context_id preimage, so changing it changes every context_id.",
|
|
16
|
+
"type": "string",
|
|
17
|
+
"minLength": 1
|
|
18
|
+
},
|
|
19
|
+
"rules": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"items": { "$ref": "#/$defs/rule" }
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"$defs": {
|
|
25
|
+
"rule": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"additionalProperties": false,
|
|
28
|
+
"required": ["tool", "mode", "participants"],
|
|
29
|
+
"properties": {
|
|
30
|
+
"tool": {
|
|
31
|
+
"description": "Tool name: an exact name, or a glob where * matches any run of characters (including none) and ? matches exactly one. No other wildcard syntax; matching is case-sensitive and covers the whole name.",
|
|
32
|
+
"type": "string",
|
|
33
|
+
"minLength": 1
|
|
34
|
+
},
|
|
35
|
+
"mode": {
|
|
36
|
+
"description": "The MACP mode of the approval session.",
|
|
37
|
+
"enum": ["macp.mode.quorum.v1", "macp.mode.decision.v1"]
|
|
38
|
+
},
|
|
39
|
+
"participants": {
|
|
40
|
+
"description": "MACP agent ids of the approvers. The gate itself is the session initiator and never one of them.",
|
|
41
|
+
"type": "array",
|
|
42
|
+
"minItems": 1,
|
|
43
|
+
"uniqueItems": true,
|
|
44
|
+
"items": { "type": "string", "minLength": 1 }
|
|
45
|
+
},
|
|
46
|
+
"threshold": {
|
|
47
|
+
"description": "APPROVE ballots (Quorum) or votes (Decision) needed to forward the call. Defaults to a strict majority of participants: floor(n/2) + 1. Must not exceed the participant count. With a bound policy that overrides the threshold (for example policy.packs.two-agent-rule), set the same value here.",
|
|
48
|
+
"type": "integer",
|
|
49
|
+
"minimum": 1
|
|
50
|
+
},
|
|
51
|
+
"ttl_ms": {
|
|
52
|
+
"description": "Session TTL. A session still open after ttl_ms plus a 5000 ms grace is cancelled by the gate and the call refused.",
|
|
53
|
+
"type": "integer",
|
|
54
|
+
"minimum": 1,
|
|
55
|
+
"default": 300000
|
|
56
|
+
},
|
|
57
|
+
"policy_version": {
|
|
58
|
+
"description": "An RFC-MACP-0012 policy registered with the runtime, bound to the session at SessionStart and evaluated at commitment. The gate must keep commitment authority (initiator_only or any_participant); a designated_role policy such as policy.packs.human-signoff cannot be used. Omitted means the runtime default policy.",
|
|
59
|
+
"type": "string",
|
|
60
|
+
"minLength": 1
|
|
61
|
+
},
|
|
62
|
+
"description": {
|
|
63
|
+
"description": "Free text for humans; ignored by the gate.",
|
|
64
|
+
"type": "string",
|
|
65
|
+
"minLength": 1
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contract_version": "1",
|
|
3
|
+
"description": "What a gated tool call puts on the MACP wire, and what the MCP caller gets back. Strings with {placeholders} are templates: substitute literally, no other formatting. {server} is gate.json's server, {tool} the tool name, {context_id} and {preimage} as in context-id-vectors.json, {gate} the gate's MACP_AGENT_ID, {threshold} the rule's effective threshold, {session_id} the approval session's id, {state} its SessionState name without the SESSION_STATE_ prefix (EXPIRED, CANCELLED), {runtime_message} the runtime's Ack failure message.",
|
|
4
|
+
"session_start": {
|
|
5
|
+
"intent": "Approve MCP tool call {server}/{tool}",
|
|
6
|
+
"context_id": "{context_id}",
|
|
7
|
+
"ttl_ms": "rule.ttl_ms",
|
|
8
|
+
"policy_version": "rule.policy_version, or the runtime default when omitted",
|
|
9
|
+
"initiator": "{gate}"
|
|
10
|
+
},
|
|
11
|
+
"modes": {
|
|
12
|
+
"macp.mode.quorum.v1": {
|
|
13
|
+
"participants": "rule.participants",
|
|
14
|
+
"first_message": {
|
|
15
|
+
"message_type": "ApprovalRequest",
|
|
16
|
+
"request_id": "{context_id}",
|
|
17
|
+
"action": "tools/call:{tool}",
|
|
18
|
+
"summary": "{server}/{tool}",
|
|
19
|
+
"details": "UTF-8 bytes of {preimage}",
|
|
20
|
+
"required_approvals": "{threshold}"
|
|
21
|
+
},
|
|
22
|
+
"ballots": "The first Approve / Reject / Abstain from each approver for request_id {context_id} counts (the runtime rejects a second one); Approve is an approval."
|
|
23
|
+
},
|
|
24
|
+
"macp.mode.decision.v1": {
|
|
25
|
+
"participants": "[{gate}] followed by rule.participants; the gate never votes and is not counted",
|
|
26
|
+
"first_message": {
|
|
27
|
+
"message_type": "Proposal",
|
|
28
|
+
"proposal_id": "call",
|
|
29
|
+
"option": "tools/call:{tool}",
|
|
30
|
+
"rationale": "{preimage}"
|
|
31
|
+
},
|
|
32
|
+
"ballots": "The first Vote from each approver on proposal_id call counts (the runtime rejects a second one); APPROVE (case-insensitive) is an approval, REJECT and ABSTAIN are not."
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"outcome": {
|
|
36
|
+
"approve_when": "approvals >= {threshold}",
|
|
37
|
+
"deny_when": "approvals + approvers_yet_to_vote < {threshold}",
|
|
38
|
+
"counted": "Only ballots from rule.participants."
|
|
39
|
+
},
|
|
40
|
+
"commitment": {
|
|
41
|
+
"approve": {
|
|
42
|
+
"action": "macp.gate.approve",
|
|
43
|
+
"outcome_positive": true
|
|
44
|
+
},
|
|
45
|
+
"deny": {
|
|
46
|
+
"action": "macp.gate.deny",
|
|
47
|
+
"outcome_positive": false
|
|
48
|
+
},
|
|
49
|
+
"authority_scope": "tools/call:{server}/{tool}",
|
|
50
|
+
"reason": "Free text; not part of the contract.",
|
|
51
|
+
"reported": "The Commitment envelope the runtime records for the session (its payload bytes are what commitment_hash covers)."
|
|
52
|
+
},
|
|
53
|
+
"forwarded_result": {
|
|
54
|
+
"meta_key": "io.multiagentcoordinationprotocol/commitment",
|
|
55
|
+
"meta_value": {
|
|
56
|
+
"session_id": "the approval session id",
|
|
57
|
+
"mode": "rule.mode",
|
|
58
|
+
"outcome": "approve",
|
|
59
|
+
"commitment_hash": "RFC-MACP-0013 hash of the recorded Commitment payload"
|
|
60
|
+
},
|
|
61
|
+
"note": "Added to the upstream's CallToolResult _meta; every key the upstream set is preserved. outcome is \"approve\" or \"deny\"; only forwarded calls carry the key, so it is always \"approve\" there."
|
|
62
|
+
},
|
|
63
|
+
"refusals": {
|
|
64
|
+
"note": "The upstream is never called. The result is an MCP tool error (isError: true) whose only content is one text block with exactly the rendered template.",
|
|
65
|
+
"templates": {
|
|
66
|
+
"denied": "POLICY_DENIED: tool call {tool} denied by session {session_id}",
|
|
67
|
+
"policy_denied": "POLICY_DENIED: {runtime_message}",
|
|
68
|
+
"not_open": "SESSION_NOT_OPEN: session {session_id} ended {state} without a commitment",
|
|
69
|
+
"unsafe_integer": "INVALID_ARGUMENT: tool call {tool} arguments contain an integer outside ±(2^53−1), which has no canonical JSON form"
|
|
70
|
+
},
|
|
71
|
+
"cases": {
|
|
72
|
+
"denied": "The session committed outcome_positive = false (the threshold became unreachable).",
|
|
73
|
+
"policy_denied": "The runtime rejected the gate's positive Commitment with POLICY_DENIED (a bound RFC-0012 policy); the gate cancels the session.",
|
|
74
|
+
"not_open": "The session ended EXPIRED or CANCELLED with no Commitment. A session still OPEN at ttl_ms + expiry_grace_ms is cancelled by the gate first, so {state} is CANCELLED there.",
|
|
75
|
+
"unsafe_integer": "The arguments hold a number that is integral with |v| > 2^53−1 (Python: an int, or a float with is_integer(); TypeScript: Number.isInteger(v) && !Number.isSafeInteger(v)), at any depth. Checked before any session is opened.",
|
|
76
|
+
"other": "Any other failure (runtime unreachable, a rejected SessionStart, ...) is \"<CODE>: <message>\" with the base server's code precedence: Ack failure code, then the macp-error-code trailer, then the gRPC status name. Not a template; codes and messages vary."
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"timing": {
|
|
80
|
+
"expiry_grace_ms": 5000
|
|
81
|
+
}
|
|
82
|
+
}
|
package/dist/bridge.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `get_commitment` reflect every participant's messages, not just this
|
|
10
10
|
* agent's. Mirrors the Python macp-mcp `bridge.py` call for call.
|
|
11
11
|
*/
|
|
12
|
-
import { MacpClient, type Envelope } from 'macp-sdk-typescript';
|
|
12
|
+
import { MacpClient, type CommitmentPayload, type Envelope } from 'macp-sdk-typescript';
|
|
13
13
|
import { type Settings } from './config';
|
|
14
14
|
import type { CommitAckResult, CommitmentResult, ProposalAckResult, SessionResult, StartSessionResult, VerifyResult, VoteAckResult } from './results';
|
|
15
15
|
/** RFC-MACP-0013 hash label, the `label` part of the hash preimage. */
|
|
@@ -26,6 +26,8 @@ export type ClientFactory = (settings: Settings) => BridgeClient;
|
|
|
26
26
|
export declare function defaultClientFactory(settings: Settings): BridgeClient;
|
|
27
27
|
/** SessionState enum name without the `SESSION_STATE_` prefix (OPEN, RESOLVED, ...). */
|
|
28
28
|
export declare function stateName(state: string | undefined): string;
|
|
29
|
+
/** A decoded CommitmentPayload with proto3 defaults filled in (protobufjs omits them). Commitment is a core message, so any mode decodes it. */
|
|
30
|
+
export declare function decodeCommitment(client: BridgeClient, wire: Buffer): CommitmentPayload;
|
|
29
31
|
/** Decision-mode actions for one MACP agent identity. */
|
|
30
32
|
export declare class MacpBridge {
|
|
31
33
|
readonly settings: Settings;
|
package/dist/bridge.js
CHANGED
|
@@ -14,6 +14,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
14
|
exports.MacpBridge = exports.ToolArgumentError = exports.REPLAY_MAX_MS = exports.COMMITMENT_HASH_LABEL = void 0;
|
|
15
15
|
exports.defaultClientFactory = defaultClientFactory;
|
|
16
16
|
exports.stateName = stateName;
|
|
17
|
+
exports.decodeCommitment = decodeCommitment;
|
|
17
18
|
const macp_sdk_typescript_1 = require("macp-sdk-typescript");
|
|
18
19
|
const config_1 = require("./config");
|
|
19
20
|
const version_1 = require("./version");
|
|
@@ -56,7 +57,7 @@ function ackDict(ack) {
|
|
|
56
57
|
function int64(value) {
|
|
57
58
|
return value === undefined || value === '' ? 0 : Number(value);
|
|
58
59
|
}
|
|
59
|
-
/** A decoded CommitmentPayload with proto3 defaults filled in (protobufjs omits them). */
|
|
60
|
+
/** A decoded CommitmentPayload with proto3 defaults filled in (protobufjs omits them). Commitment is a core message, so any mode decodes it. */
|
|
60
61
|
function decodeCommitment(client, wire) {
|
|
61
62
|
const raw = client.protoRegistry.decodeKnownPayload(macp_sdk_typescript_1.MODE_DECISION, 'Commitment', wire) ?? {};
|
|
62
63
|
const str = (key) => (typeof raw[key] === 'string' ? raw[key] : '');
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Entry point (stdio transport): `macp-mcp` serves the Decision tools;
|
|
4
|
+
* `macp-mcp gate ...` proxies an upstream MCP server behind gate rules.
|
|
5
|
+
* stdout carries only the MCP protocol; everything else goes to stderr.
|
|
6
|
+
*/
|
|
3
7
|
export {};
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Entry point (stdio transport): `macp-mcp` serves the Decision tools;
|
|
5
|
+
* `macp-mcp gate ...` proxies an upstream MCP server behind gate rules.
|
|
6
|
+
* stdout carries only the MCP protocol; everything else goes to stderr.
|
|
7
|
+
*/
|
|
4
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
9
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
10
|
const macp_sdk_typescript_1 = require("macp-sdk-typescript");
|
|
7
11
|
const config_1 = require("./config");
|
|
12
|
+
const cli_1 = require("./gate/cli");
|
|
8
13
|
const server_1 = require("./server");
|
|
9
14
|
async function main() {
|
|
10
15
|
// The SDK's default sink sends debug/info to console.log, i.e. stdout; route every level to stderr.
|
|
@@ -12,8 +17,15 @@ async function main() {
|
|
|
12
17
|
sink: (level, args) => process.stderr.write(`[macp-sdk] ${level} ${args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ')}\n`),
|
|
13
18
|
});
|
|
14
19
|
let settings;
|
|
20
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
15
21
|
try {
|
|
16
22
|
settings = (0, config_1.loadSettings)();
|
|
23
|
+
if (command === 'gate') {
|
|
24
|
+
process.stderr.write(`macp-mcp gate: agent=${settings.agentId} runtime=${settings.target} ` +
|
|
25
|
+
`auth=${(0, config_1.authMode)(settings)} tls=${settings.allowInsecure ? 'off' : 'on'}\n`);
|
|
26
|
+
await (0, cli_1.runGate)(rest, settings);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
17
29
|
}
|
|
18
30
|
catch (err) {
|
|
19
31
|
if (err instanceof config_1.ConfigError) {
|