@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.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Runs the MACP session that decides one gated tool call.
3
+ *
4
+ * The gate is the session's initiator and its only committer. In Quorum it
5
+ * sends one ApprovalRequest and is not itself a participant (the runtime lets
6
+ * only the initiator request approval; ballots come from the declared
7
+ * participants). In Decision it must be a participant to send its Proposal,
8
+ * so it is added as the first participant, but it never votes and is not
9
+ * counted. Either way the gate watches the session's stream and commits as
10
+ * soon as the outcome is settled: positive once the approvers' first ballots
11
+ * reach the rule's threshold, negative once they no longer can.
12
+ *
13
+ * What approvers see on the wire (intent, request fields, commitment action
14
+ * and scope) is pinned by `contract/gate/session.json`, shared with the
15
+ * Python macp-mcp, so a session opened by either gate looks identical.
16
+ */
17
+ import { type BridgeClient } from '../bridge';
18
+ import type { Settings } from '../config';
19
+ import { type GateMode, type GateRule } from './config';
20
+ import { type GateTemplate } from './contract';
21
+ /** Decision-mode proposal id: one gated call per session. */
22
+ export declare const GATE_PROPOSAL_ID = "call";
23
+ export declare const GATE_ACTION_APPROVE = "macp.gate.approve";
24
+ export declare const GATE_ACTION_DENY = "macp.gate.deny";
25
+ /** How long past `ttl_ms` the gate waits for the runtime before cancelling the session itself. */
26
+ export declare const GATE_GRACE_MS = 5000;
27
+ /** How often the gate re-checks the session state while no envelope arrives. */
28
+ export declare const GATE_POLL_MS = 500;
29
+ export type GateOutcome = 'approve' | 'deny';
30
+ /** The settled session behind a gated call (`_meta["io.multiagentcoordinationprotocol/commitment"]`). */
31
+ export interface GateCommitment {
32
+ session_id: string;
33
+ mode: GateMode;
34
+ outcome: GateOutcome;
35
+ commitment_hash: string;
36
+ }
37
+ /** A gated call that cannot go ahead. `code` is a registry error code; the tool error text is `"<code>: <message>"`. */
38
+ export declare class GateRefusal extends Error {
39
+ readonly code: 'POLICY_DENIED' | 'SESSION_NOT_OPEN' | 'INVALID_ARGUMENT';
40
+ readonly sessionId: string;
41
+ constructor(code: 'POLICY_DENIED' | 'SESSION_NOT_OPEN' | 'INVALID_ARGUMENT', message: string, sessionId: string);
42
+ }
43
+ /** A refusal whose `"<code>: <message>"` text is exactly the rendered `session.json` template. */
44
+ export declare function refusal(template: GateTemplate, vars: Record<string, string>, sessionId?: string): GateRefusal;
45
+ export interface GatedCall {
46
+ server: string;
47
+ tool: string;
48
+ /** JCS preimage of {"arguments","server","tool"}. */
49
+ preimage: string;
50
+ contextId: string;
51
+ }
52
+ /** Whether the approvers' ballots settle the call: approve at the bar, deny once it is out of reach. */
53
+ export declare function settle(approvals: number, cast: number, approvers: number, required: number): GateOutcome | undefined;
54
+ export declare class GateApprover {
55
+ private readonly client;
56
+ private readonly settings;
57
+ private readonly clock;
58
+ constructor(client: BridgeClient, settings: Settings, clock?: () => number);
59
+ /** Open a session for `call` under `rule` and wait for it to settle. */
60
+ decide(rule: GateRule, call: GatedCall): Promise<GateCommitment>;
61
+ private state;
62
+ private cancel;
63
+ private watch;
64
+ }
65
+ /** The refusal for a settled `deny`. */
66
+ export declare function denied(commitment: GateCommitment, tool: string): GateRefusal;
@@ -0,0 +1,202 @@
1
+ "use strict";
2
+ /**
3
+ * Runs the MACP session that decides one gated tool call.
4
+ *
5
+ * The gate is the session's initiator and its only committer. In Quorum it
6
+ * sends one ApprovalRequest and is not itself a participant (the runtime lets
7
+ * only the initiator request approval; ballots come from the declared
8
+ * participants). In Decision it must be a participant to send its Proposal,
9
+ * so it is added as the first participant, but it never votes and is not
10
+ * counted. Either way the gate watches the session's stream and commits as
11
+ * soon as the outcome is settled: positive once the approvers' first ballots
12
+ * reach the rule's threshold, negative once they no longer can.
13
+ *
14
+ * What approvers see on the wire (intent, request fields, commitment action
15
+ * and scope) is pinned by `contract/gate/session.json`, shared with the
16
+ * Python macp-mcp, so a session opened by either gate looks identical.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.GateApprover = exports.GateRefusal = exports.GATE_POLL_MS = exports.GATE_GRACE_MS = exports.GATE_ACTION_DENY = exports.GATE_ACTION_APPROVE = exports.GATE_PROPOSAL_ID = void 0;
20
+ exports.refusal = refusal;
21
+ exports.settle = settle;
22
+ exports.denied = denied;
23
+ const macp_sdk_typescript_1 = require("macp-sdk-typescript");
24
+ const bridge_1 = require("../bridge");
25
+ const config_1 = require("./config");
26
+ const contract_1 = require("./contract");
27
+ /** Decision-mode proposal id: one gated call per session. */
28
+ exports.GATE_PROPOSAL_ID = 'call';
29
+ exports.GATE_ACTION_APPROVE = 'macp.gate.approve';
30
+ exports.GATE_ACTION_DENY = 'macp.gate.deny';
31
+ /** How long past `ttl_ms` the gate waits for the runtime before cancelling the session itself. */
32
+ exports.GATE_GRACE_MS = 5_000;
33
+ /** How often the gate re-checks the session state while no envelope arrives. */
34
+ exports.GATE_POLL_MS = 500;
35
+ /** A gated call that cannot go ahead. `code` is a registry error code; the tool error text is `"<code>: <message>"`. */
36
+ class GateRefusal extends Error {
37
+ code;
38
+ sessionId;
39
+ constructor(code, message, sessionId) {
40
+ super(message);
41
+ this.code = code;
42
+ this.sessionId = sessionId;
43
+ this.name = 'GateRefusal';
44
+ }
45
+ }
46
+ exports.GateRefusal = GateRefusal;
47
+ /** A refusal whose `"<code>: <message>"` text is exactly the rendered `session.json` template. */
48
+ function refusal(template, vars, sessionId = '') {
49
+ const text = (0, contract_1.renderRefusal)(template, vars);
50
+ const at = text.indexOf(': ');
51
+ return new GateRefusal(text.slice(0, at), text.slice(at + 2), sessionId);
52
+ }
53
+ /** Whether the approvers' ballots settle the call: approve at the bar, deny once it is out of reach. */
54
+ function settle(approvals, cast, approvers, required) {
55
+ if (approvals >= required)
56
+ return 'approve';
57
+ if (approvals + (approvers - cast) < required)
58
+ return 'deny';
59
+ return undefined;
60
+ }
61
+ function tracker(projection, client, rule, ballots, isApprove) {
62
+ const approvers = new Set(rule.participants);
63
+ return {
64
+ apply: (envelope) => projection.applyEnvelope(envelope, client.protoRegistry),
65
+ outcome: () => {
66
+ // The first ballot per approver counts (session.json): the runtime rejects a second one, and
67
+ // the SDK projections, keyed by sender, keep the first ballot either way.
68
+ const counted = [...ballots()].filter((b) => approvers.has(b.sender));
69
+ const approvals = counted.filter(isApprove).length;
70
+ return settle(approvals, counted.length, approvers.size, (0, config_1.requiredApprovals)(rule));
71
+ },
72
+ };
73
+ }
74
+ class GateApprover {
75
+ client;
76
+ settings;
77
+ clock;
78
+ constructor(client, settings, clock = Date.now) {
79
+ this.client = client;
80
+ this.settings = settings;
81
+ this.clock = clock;
82
+ }
83
+ /** Open a session for `call` under `rule` and wait for it to settle. */
84
+ async decide(rule, call) {
85
+ const client = this.client;
86
+ const options = rule.policy_version ? { policyVersion: rule.policy_version } : {};
87
+ const ttlMs = rule.ttl_ms ?? config_1.GATE_DEFAULT_TTL_MS;
88
+ const intent = `Approve MCP tool call ${call.server}/${call.tool}`;
89
+ const deadline = this.clock() + ttlMs + exports.GATE_GRACE_MS;
90
+ let session;
91
+ let track;
92
+ if (rule.mode === macp_sdk_typescript_1.MODE_QUORUM) {
93
+ const quorum = new macp_sdk_typescript_1.QuorumSession(client, options);
94
+ await quorum.start({ intent, participants: rule.participants, ttlMs, contextId: call.contextId });
95
+ await quorum.requestApproval({
96
+ requestId: call.contextId,
97
+ action: `tools/call:${call.tool}`,
98
+ summary: `${call.server}/${call.tool}`,
99
+ details: Buffer.from(call.preimage, 'utf8'),
100
+ requiredApprovals: (0, config_1.requiredApprovals)(rule),
101
+ });
102
+ const projection = new macp_sdk_typescript_1.QuorumProjection();
103
+ track = tracker(projection, this.client, rule, () => projection.ballots.get(call.contextId)?.values() ?? [], (b) => b.vote === 'approve');
104
+ session = quorum;
105
+ }
106
+ else {
107
+ const decision = new macp_sdk_typescript_1.DecisionSession(client, options);
108
+ const participants = [this.settings.agentId, ...rule.participants];
109
+ await decision.start({ intent, participants, ttlMs, contextId: call.contextId });
110
+ await decision.propose({
111
+ proposalId: exports.GATE_PROPOSAL_ID,
112
+ option: `tools/call:${call.tool}`,
113
+ rationale: call.preimage,
114
+ });
115
+ const projection = new macp_sdk_typescript_1.DecisionProjection();
116
+ track = tracker(projection, this.client, rule, () => projection.votes.get(exports.GATE_PROPOSAL_ID)?.values() ?? [], (v) => v.vote.toUpperCase() === 'APPROVE');
117
+ session = decision;
118
+ }
119
+ return this.watch(session, rule.mode, track, call, deadline);
120
+ }
121
+ async state(sessionId) {
122
+ return (0, bridge_1.stateName)((await this.client.getSession(sessionId)).metadata.state);
123
+ }
124
+ async cancel(session, reason) {
125
+ try {
126
+ await session.cancel(reason);
127
+ }
128
+ catch {
129
+ // Best effort: the session may already have ended.
130
+ }
131
+ }
132
+ async watch(session, mode, track, call, deadline) {
133
+ const sessionId = session.sessionId;
134
+ const notOpen = (state) => refusal('not_open', { session_id: sessionId, state, tool: call.tool }, sessionId);
135
+ const stream = this.client.openStream();
136
+ let committing = false;
137
+ try {
138
+ await stream.sendSubscribe(sessionId, 0);
139
+ for (;;) {
140
+ let envelope = null;
141
+ try {
142
+ envelope = await stream.read(exports.GATE_POLL_MS);
143
+ }
144
+ catch (err) {
145
+ if (!(err instanceof macp_sdk_typescript_1.MacpTimeoutError))
146
+ throw err;
147
+ }
148
+ if (!envelope) {
149
+ const state = await this.state(sessionId);
150
+ if (state !== 'OPEN')
151
+ throw notOpen(state);
152
+ if (this.clock() >= deadline) {
153
+ await this.cancel(session, 'gate deadline passed without a commitment');
154
+ throw notOpen(await this.state(sessionId));
155
+ }
156
+ continue;
157
+ }
158
+ if (envelope.sessionId !== sessionId)
159
+ continue;
160
+ track.apply(envelope);
161
+ if (envelope.messageType === 'Commitment') {
162
+ const payload = (0, bridge_1.decodeCommitment)(this.client, Buffer.from(envelope.payload));
163
+ const outcome = payload.outcomePositive ? 'approve' : 'deny';
164
+ return { session_id: sessionId, mode, outcome, commitment_hash: (0, macp_sdk_typescript_1.commitmentHash)(payload) };
165
+ }
166
+ const outcome = committing ? undefined : track.outcome();
167
+ if (!outcome)
168
+ continue;
169
+ committing = true;
170
+ try {
171
+ await session.commit({
172
+ action: outcome === 'approve' ? exports.GATE_ACTION_APPROVE : exports.GATE_ACTION_DENY,
173
+ authorityScope: `tools/call:${call.server}/${call.tool}`,
174
+ reason: outcome === 'approve' ? 'approvals reached the threshold' : 'the threshold can no longer be reached',
175
+ outcomePositive: outcome === 'approve',
176
+ });
177
+ }
178
+ catch (err) {
179
+ if (!(err instanceof macp_sdk_typescript_1.MacpAckError))
180
+ throw err;
181
+ if (err.failure.code === 'SESSION_NOT_OPEN')
182
+ throw notOpen(await this.state(sessionId));
183
+ if (err.failure.code === 'POLICY_DENIED') {
184
+ // A bound RFC-MACP-0012 policy refused the commitment.
185
+ await this.cancel(session, 'commitment refused by the session policy');
186
+ throw refusal('policy_denied', { runtime_message: err.failure.message }, sessionId);
187
+ }
188
+ throw err;
189
+ }
190
+ // The Commitment arrives on the stream; the hash is taken from it there.
191
+ }
192
+ }
193
+ finally {
194
+ stream.close();
195
+ }
196
+ }
197
+ }
198
+ exports.GateApprover = GateApprover;
199
+ /** The refusal for a settled `deny`. */
200
+ function denied(commitment, tool) {
201
+ return refusal('denied', { tool, session_id: commitment.session_id }, commitment.session_id);
202
+ }
@@ -0,0 +1,14 @@
1
+ /** `macp-mcp gate --policy <gate.json> --upstream <command> [args...]` (flags pinned by `contract/gate/cli.json`). */
2
+ import { type Settings } from '../config';
3
+ export interface GateArgs {
4
+ policy: string;
5
+ upstream: {
6
+ command: string;
7
+ args: string[];
8
+ };
9
+ }
10
+ /** Parse the arguments after `gate`. `--upstream` takes the rest of argv as the upstream's command line. */
11
+ export declare function parseGateArgs(argv: string[]): GateArgs;
12
+ /** The upstream's environment: the gate's own, minus the gate's MACP credential. */
13
+ export declare function upstreamEnv(env?: NodeJS.ProcessEnv): Record<string, string>;
14
+ export declare function runGate(argv: string[], settings: Settings): Promise<void>;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ /** `macp-mcp gate --policy <gate.json> --upstream <command> [args...]` (flags pinned by `contract/gate/cli.json`). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.parseGateArgs = parseGateArgs;
5
+ exports.upstreamEnv = upstreamEnv;
6
+ exports.runGate = runGate;
7
+ const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
8
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
9
+ const stdio_js_2 = require("@modelcontextprotocol/sdk/server/stdio.js");
10
+ const bridge_1 = require("../bridge");
11
+ const config_1 = require("../config");
12
+ const version_1 = require("../version");
13
+ const approval_1 = require("./approval");
14
+ const config_2 = require("./config");
15
+ const proxy_1 = require("./proxy");
16
+ /** Parse the arguments after `gate`. `--upstream` takes the rest of argv as the upstream's command line. */
17
+ function parseGateArgs(argv) {
18
+ let policy;
19
+ for (let i = 0; i < argv.length; i++) {
20
+ const arg = argv[i];
21
+ if (arg === '--upstream') {
22
+ const [command, ...args] = argv.slice(i + 1);
23
+ if (!command)
24
+ throw new config_1.ConfigError('--upstream needs a command');
25
+ if (!policy)
26
+ throw new config_1.ConfigError('--policy <gate.json> is required');
27
+ return { policy, upstream: { command, args } };
28
+ }
29
+ if (arg === '--policy') {
30
+ policy = argv[++i];
31
+ if (!policy)
32
+ throw new config_1.ConfigError('--policy needs a file');
33
+ }
34
+ else if (arg.startsWith('--policy=')) {
35
+ policy = arg.slice('--policy='.length);
36
+ }
37
+ else {
38
+ throw new config_1.ConfigError(`unknown gate argument '${arg}'`);
39
+ }
40
+ }
41
+ throw new config_1.ConfigError('--upstream <command> [args...] is required');
42
+ }
43
+ /** The upstream's environment: the gate's own, minus the gate's MACP credential. */
44
+ function upstreamEnv(env = process.env) {
45
+ const out = {};
46
+ for (const [key, value] of Object.entries(env)) {
47
+ if (value !== undefined && key !== 'MACP_AUTH_TOKEN')
48
+ out[key] = value;
49
+ }
50
+ return out;
51
+ }
52
+ async function runGate(argv, settings) {
53
+ const args = parseGateArgs(argv);
54
+ const config = (0, config_2.loadGateConfig)(args.policy, settings.agentId);
55
+ process.stderr.write(`macp-mcp gate: server=${config.server} rules=${config.rules.length} upstream=${args.upstream.command}\n`);
56
+ const upstream = new index_js_1.Client({ name: 'macp-mcp-gate', version: version_1.VERSION });
57
+ await upstream.connect(new stdio_js_1.StdioClientTransport({
58
+ command: args.upstream.command,
59
+ args: args.upstream.args,
60
+ env: upstreamEnv(),
61
+ stderr: 'inherit',
62
+ }));
63
+ const client = (0, bridge_1.defaultClientFactory)(settings);
64
+ const server = (0, proxy_1.buildGateServer)({ config, upstream, approver: new approval_1.GateApprover(client, settings) });
65
+ const transport = new stdio_js_2.StdioServerTransport();
66
+ transport.onclose = () => {
67
+ client.close();
68
+ void upstream.close();
69
+ };
70
+ await server.connect(transport);
71
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The gate policy file (`gate.json`): which upstream tool calls need a MACP
3
+ * session's approval, and how that session is run. Validated against
4
+ * `contract/gate/gate.schema.json`, shared byte-for-byte with the Python
5
+ * macp-mcp.
6
+ */
7
+ import type { MODE_DECISION, MODE_QUORUM } from 'macp-sdk-typescript';
8
+ export type GateMode = typeof MODE_QUORUM | typeof MODE_DECISION;
9
+ export interface GateRule {
10
+ /** Exact tool name, or a glob where `*` matches any run of characters and `?` one character. */
11
+ tool: string;
12
+ mode: GateMode;
13
+ /** The approvers. */
14
+ participants: string[];
15
+ /** Approvals needed; defaults to a strict majority of `participants`. */
16
+ threshold?: number;
17
+ ttl_ms?: number;
18
+ policy_version?: string;
19
+ }
20
+ export interface GateConfig {
21
+ version: 1;
22
+ /** The upstream's name, bound into every gated call's context_id. */
23
+ server: string;
24
+ rules: GateRule[];
25
+ }
26
+ /** `ttl_ms` when a rule omits it. */
27
+ export declare const GATE_DEFAULT_TTL_MS = 300000;
28
+ /**
29
+ * Validate a parsed gate policy for the gate acting as `agentId`, beyond the
30
+ * schema: participants are unique, never the gate itself (it initiates and
31
+ * commits, it does not approve), and `threshold` is within 1..=participants.
32
+ */
33
+ export declare function parseGateConfig(raw: unknown, agentId: string): GateConfig;
34
+ export declare function loadGateConfig(file: string, agentId: string): GateConfig;
35
+ /** The first rule whose `tool` matches, or undefined (the call passes through). */
36
+ export declare function matchRule(config: GateConfig, tool: string): GateRule | undefined;
37
+ /** Approvals a rule needs: its `threshold`, else a strict majority of its approvers. */
38
+ export declare function requiredApprovals(rule: GateRule): number;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ /**
3
+ * The gate policy file (`gate.json`): which upstream tool calls need a MACP
4
+ * session's approval, and how that session is run. Validated against
5
+ * `contract/gate/gate.schema.json`, shared byte-for-byte with the Python
6
+ * macp-mcp.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42
+ return (mod && mod.__esModule) ? mod : { "default": mod };
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.GATE_DEFAULT_TTL_MS = void 0;
46
+ exports.parseGateConfig = parseGateConfig;
47
+ exports.loadGateConfig = loadGateConfig;
48
+ exports.matchRule = matchRule;
49
+ exports.requiredApprovals = requiredApprovals;
50
+ const fs = __importStar(require("node:fs"));
51
+ const _2020_1 = __importDefault(require("ajv/dist/2020"));
52
+ const config_1 = require("../config");
53
+ const contract_1 = require("./contract");
54
+ /** `ttl_ms` when a rule omits it. */
55
+ exports.GATE_DEFAULT_TTL_MS = 300_000;
56
+ /**
57
+ * Validate a parsed gate policy for the gate acting as `agentId`, beyond the
58
+ * schema: participants are unique, never the gate itself (it initiates and
59
+ * commits, it does not approve), and `threshold` is within 1..=participants.
60
+ */
61
+ function parseGateConfig(raw, agentId) {
62
+ const ajv = new _2020_1.default({ strict: false, allErrors: true });
63
+ const validate = ajv.compile((0, contract_1.loadGateSchema)());
64
+ if (!validate(raw))
65
+ throw new config_1.ConfigError(`invalid gate policy: ${ajv.errorsText(validate.errors, { dataVar: 'gate' })}`);
66
+ const config = raw;
67
+ for (const [i, rule] of config.rules.entries()) {
68
+ const n = new Set(rule.participants).size;
69
+ if (n !== rule.participants.length)
70
+ throw new config_1.ConfigError(`invalid gate policy: rules[${i}] repeats a participant`);
71
+ if (rule.participants.includes(agentId)) {
72
+ throw new config_1.ConfigError(`invalid gate policy: rules[${i}].participants includes the gate's own agent id '${agentId}'`);
73
+ }
74
+ if (rule.threshold !== undefined && rule.threshold > n) {
75
+ throw new config_1.ConfigError(`invalid gate policy: rules[${i}].threshold ${rule.threshold} exceeds ${n} participants`);
76
+ }
77
+ }
78
+ return config;
79
+ }
80
+ function loadGateConfig(file, agentId) {
81
+ let text;
82
+ try {
83
+ text = fs.readFileSync(file, 'utf8');
84
+ }
85
+ catch (err) {
86
+ throw new config_1.ConfigError(`cannot read gate policy '${file}': ${err.message}`);
87
+ }
88
+ let raw;
89
+ try {
90
+ raw = JSON.parse(text);
91
+ }
92
+ catch (err) {
93
+ throw new config_1.ConfigError(`gate policy '${file}' is not JSON: ${err.message}`);
94
+ }
95
+ return parseGateConfig(raw, agentId);
96
+ }
97
+ function globToRegExp(glob) {
98
+ const body = glob
99
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
100
+ .replace(/\*/g, '.*')
101
+ .replace(/\?/g, '.');
102
+ return new RegExp(`^${body}$`, 's');
103
+ }
104
+ /** The first rule whose `tool` matches, or undefined (the call passes through). */
105
+ function matchRule(config, tool) {
106
+ return config.rules.find((rule) => rule.tool === tool || globToRegExp(rule.tool).test(tool));
107
+ }
108
+ /** Approvals a rule needs: its `threshold`, else a strict majority of its approvers. */
109
+ function requiredApprovals(rule) {
110
+ return rule.threshold ?? Math.floor(rule.participants.length / 2) + 1;
111
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The JCS (RFC 8785) form of `{"arguments", "server", "tool"}` for one call.
3
+ * Approvers see it on the wire (ApprovalRequest.details / Proposal.rationale)
4
+ * and can recompute the context_id from it.
5
+ */
6
+ export declare function gatePreimage(server: string, tool: string, args: Record<string, unknown> | undefined): string;
7
+ /** SessionStartPayload.context_id for one call: `sha256:` + lowercase hex SHA-256 of {@link gatePreimage}. */
8
+ export declare function gateContextId(server: string, tool: string, args: Record<string, unknown> | undefined): string;
9
+ export declare function contextIdOf(preimage: string): string;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.gatePreimage = gatePreimage;
4
+ exports.gateContextId = gateContextId;
5
+ exports.contextIdOf = contextIdOf;
6
+ const node_crypto_1 = require("node:crypto");
7
+ const jcs_1 = require("./jcs");
8
+ /**
9
+ * The JCS (RFC 8785) form of `{"arguments", "server", "tool"}` for one call.
10
+ * Approvers see it on the wire (ApprovalRequest.details / Proposal.rationale)
11
+ * and can recompute the context_id from it.
12
+ */
13
+ function gatePreimage(server, tool, args) {
14
+ return (0, jcs_1.canonicalize)({ arguments: args ?? {}, server, tool });
15
+ }
16
+ /** SessionStartPayload.context_id for one call: `sha256:` + lowercase hex SHA-256 of {@link gatePreimage}. */
17
+ function gateContextId(server, tool, args) {
18
+ return contextIdOf(gatePreimage(server, tool, args));
19
+ }
20
+ function contextIdOf(preimage) {
21
+ return `sha256:${(0, node_crypto_1.createHash)('sha256').update(preimage, 'utf8').digest('hex')}`;
22
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The gate's shared contract (`contract/gate/`), vendored byte-for-byte from
3
+ * multiagentcoordinationprotocol/macp-mcp like `contract/tools.json`;
4
+ * `make verify-contract` fails on drift.
5
+ */
6
+ /** Resolves from both `src/gate/` and `dist/gate/`. */
7
+ export declare const GATE_CONTRACT_DIR: string;
8
+ export declare function loadGateSchema(): object;
9
+ export type GateTemplate = 'denied' | 'policy_denied' | 'not_open' | 'unsafe_integer';
10
+ /** The server identity pinned by `cli.json`. */
11
+ export declare const GATE_SERVER: {
12
+ name: string;
13
+ instructions: string;
14
+ };
15
+ /**
16
+ * Renders a `session.json` refusal template: each `{name}` is replaced
17
+ * literally, with no other formatting, as the contract requires.
18
+ */
19
+ export declare function renderRefusal(template: GateTemplate, vars: Record<string, string>): string;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ /**
3
+ * The gate's shared contract (`contract/gate/`), vendored byte-for-byte from
4
+ * multiagentcoordinationprotocol/macp-mcp like `contract/tools.json`;
5
+ * `make verify-contract` fails on drift.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.GATE_SERVER = exports.GATE_CONTRACT_DIR = void 0;
42
+ exports.loadGateSchema = loadGateSchema;
43
+ exports.renderRefusal = renderRefusal;
44
+ const fs = __importStar(require("node:fs"));
45
+ const path = __importStar(require("node:path"));
46
+ /** Resolves from both `src/gate/` and `dist/gate/`. */
47
+ exports.GATE_CONTRACT_DIR = path.join(__dirname, '..', '..', 'contract', 'gate');
48
+ function loadGateSchema() {
49
+ return JSON.parse(fs.readFileSync(path.join(exports.GATE_CONTRACT_DIR, 'gate.schema.json'), 'utf8'));
50
+ }
51
+ function readContract(name) {
52
+ return JSON.parse(fs.readFileSync(path.join(exports.GATE_CONTRACT_DIR, name), 'utf8'));
53
+ }
54
+ const SESSION = readContract('session.json');
55
+ const CLI = readContract('cli.json');
56
+ /** The server identity pinned by `cli.json`. */
57
+ exports.GATE_SERVER = CLI.server;
58
+ /**
59
+ * Renders a `session.json` refusal template: each `{name}` is replaced
60
+ * literally, with no other formatting, as the contract requires.
61
+ */
62
+ function renderRefusal(template, vars) {
63
+ return SESSION.refusals.templates[template].replace(/\{([a-z_]+)\}/g, (whole, key) => vars[key] ?? whole);
64
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * RFC 8785 JSON Canonicalization Scheme (JCS) for the gate's context_id.
3
+ *
4
+ * JCS is defined on top of ECMAScript's own JSON serialization: strings and
5
+ * numbers are emitted exactly as `JSON.stringify` emits them (RFC 8785 §3.2.2),
6
+ * and object members are sorted by their names' UTF-16 code units (§3.2.3),
7
+ * which is what the default `Array.prototype.sort` comparison does.
8
+ */
9
+ export declare class CanonicalizationError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ export declare function canonicalize(value: unknown): string;
13
+ /**
14
+ * Whether `value` holds, at any depth, an integral number outside ±(2^53−1).
15
+ * Such a number has no canonical form: JSON.parse may already have rounded it,
16
+ * so an approver would sign off on a value other than the one forwarded.
17
+ */
18
+ export declare function hasUnsafeInteger(value: unknown): boolean;