@agentwares/agentguard 0.1.7 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +108 -0
- package/README.md +223 -16
- package/dist/audit-5LUDXZLG.js +18 -0
- package/dist/audit-5LUDXZLG.js.map +1 -0
- package/dist/chunk-2QDPPTPY.js +179 -0
- package/dist/chunk-2QDPPTPY.js.map +1 -0
- package/dist/chunk-4WTFMFRY.js +1270 -0
- package/dist/chunk-4WTFMFRY.js.map +1 -0
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-5WRI5ZAA.js.map +1 -0
- package/dist/chunk-DEVXG3IB.js +113 -0
- package/dist/chunk-DEVXG3IB.js.map +1 -0
- package/dist/chunk-F73VDY3W.js +202 -0
- package/dist/chunk-F73VDY3W.js.map +1 -0
- package/dist/chunk-IEJK7N3B.js +275 -0
- package/dist/chunk-IEJK7N3B.js.map +1 -0
- package/dist/chunk-IS3DAHZI.js +1698 -0
- package/dist/chunk-IS3DAHZI.js.map +1 -0
- package/dist/chunk-JSELR4SG.js +780 -0
- package/dist/chunk-JSELR4SG.js.map +1 -0
- package/dist/chunk-OM5L7A32.js +199 -0
- package/dist/chunk-OM5L7A32.js.map +1 -0
- package/dist/chunk-SWZQMPDG.js +206 -0
- package/dist/chunk-SWZQMPDG.js.map +1 -0
- package/dist/chunk-TJXZBOHL.js +529 -0
- package/dist/chunk-TJXZBOHL.js.map +1 -0
- package/dist/chunk-XOBFANL4.js +119 -0
- package/dist/chunk-XOBFANL4.js.map +1 -0
- package/dist/chunk-XPNBYSIN.js +23 -0
- package/dist/chunk-XPNBYSIN.js.map +1 -0
- package/dist/chunk-YBVD6HTT.js +1036 -0
- package/dist/chunk-YBVD6HTT.js.map +1 -0
- package/dist/chunk-ZG4LGUJE.js +847 -0
- package/dist/chunk-ZG4LGUJE.js.map +1 -0
- package/dist/cli.js +10 -4775
- package/dist/cli.js.map +1 -1
- package/dist/fixtures/crm-server.js +6 -1272
- package/dist/fixtures/crm-server.js.map +1 -1
- package/dist/fixtures/demo-agent.js +4 -115
- package/dist/fixtures/demo-agent.js.map +1 -1
- package/dist/index.d.ts +73 -14
- package/dist/index.js +247 -5138
- package/dist/index.js.map +1 -1
- package/dist/init-MKPQU2JG.js +17 -0
- package/dist/init-MKPQU2JG.js.map +1 -0
- package/dist/proxy-6GP63TLD.js +146 -0
- package/dist/proxy-6GP63TLD.js.map +1 -0
- package/dist/tools-XS7PMTLT.js +15 -0
- package/dist/tools-XS7PMTLT.js.map +1 -0
- package/llms.txt +10 -4
- package/package.json +16 -4
- package/server.json +9 -9
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createHttpHandler,
|
|
3
|
+
createToolContext,
|
|
4
|
+
defineTool,
|
|
5
|
+
listToolManifest,
|
|
6
|
+
localhostHosts,
|
|
7
|
+
toolError
|
|
8
|
+
} from "./chunk-IS3DAHZI.js";
|
|
9
|
+
import {
|
|
10
|
+
RUN
|
|
11
|
+
} from "./chunk-XPNBYSIN.js";
|
|
12
|
+
import {
|
|
13
|
+
registrationPath,
|
|
14
|
+
version
|
|
15
|
+
} from "./chunk-4WTFMFRY.js";
|
|
16
|
+
|
|
17
|
+
// src/proxy/own-tools.ts
|
|
18
|
+
import {
|
|
19
|
+
buildReport,
|
|
20
|
+
latestRunId,
|
|
21
|
+
listRuns,
|
|
22
|
+
parseAuditJsonl,
|
|
23
|
+
renderReportMarkdown,
|
|
24
|
+
resolveToolName,
|
|
25
|
+
verifyChain
|
|
26
|
+
} from "@agentwares/agentguard-core";
|
|
27
|
+
import { existsSync, readFileSync } from "fs";
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
var INIT_COMMAND = `${RUN} init`;
|
|
30
|
+
var READ_ONLY = {
|
|
31
|
+
readOnlyHint: true,
|
|
32
|
+
destructiveHint: false,
|
|
33
|
+
idempotentHint: true,
|
|
34
|
+
openWorldHint: false
|
|
35
|
+
};
|
|
36
|
+
var runIdInput = z.string().min(1).max(128).optional().describe(
|
|
37
|
+
"Run to read, as it appears in the audit log (for example run_20261007093015_ab12, or the X-Run-Id an HTTP client sent). Omit it for the most recent run."
|
|
38
|
+
);
|
|
39
|
+
var counters = z.record(z.string(), z.number()).describe(
|
|
40
|
+
"counter name (tool_calls, writes, deletes, emails, spend_usd or a custom one) \u2192 value"
|
|
41
|
+
);
|
|
42
|
+
function readAudit(path) {
|
|
43
|
+
if (!existsSync(path)) return { exists: false, entries: [] };
|
|
44
|
+
try {
|
|
45
|
+
return { exists: true, entries: parseAuditJsonl(readFileSync(path, "utf8")) };
|
|
46
|
+
} catch (err) {
|
|
47
|
+
return { exists: true, entries: [], error: err instanceof Error ? err.message : String(err) };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function unreadable(path, error) {
|
|
51
|
+
return toolError({
|
|
52
|
+
code: "AUDIT_UNREADABLE",
|
|
53
|
+
cause: `the audit log at ${path} could not be read: ${error}`,
|
|
54
|
+
fix: "call okgate_verify_audit_log to see where it breaks; a hand-edited or truncated line has to be restored from a copy",
|
|
55
|
+
retryable: false
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function numbers(caps) {
|
|
59
|
+
const out = {};
|
|
60
|
+
for (const [k, v] of Object.entries(caps)) if (typeof v === "number") out[k] = v;
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
function nextStep(runtime) {
|
|
64
|
+
if (!runtime.configured)
|
|
65
|
+
return `okgate is guarding nothing: no policy file at ${runtime.loaded.path}. Run \`${INIT_COMMAND}\` in the project directory: it writes okgate.yaml in dry-run mode and routes the project's MCP servers through okgate (backup kept, \`--undo\` restores). Then restart the MCP client.`;
|
|
66
|
+
if (runtime.policy.upstreams.length === 0)
|
|
67
|
+
return `${runtime.loaded.path} lists no upstream servers, so okgate is guarding nothing. Add the MCP servers your agent uses under \`upstreams:\` in that file (it has commented examples), then restart the MCP client.`;
|
|
68
|
+
return "okgate is running; run your agent, then read okgate_get_report.";
|
|
69
|
+
}
|
|
70
|
+
var StatusOutput = z.object({
|
|
71
|
+
configured: z.boolean().describe("true when a policy file was found and loaded; false means okgate guards nothing"),
|
|
72
|
+
policy_path: z.string().describe("absolute path of the policy file read, or looked for"),
|
|
73
|
+
mode: z.enum(["dry-run", "enforce"]).describe("dry-run fakes writes and spends and records them; enforce lets allowed calls run"),
|
|
74
|
+
upstreams: z.array(
|
|
75
|
+
z.object({
|
|
76
|
+
name: z.string().describe("upstream name from the policy"),
|
|
77
|
+
connected: z.boolean().describe("whether okgate is connected to it"),
|
|
78
|
+
tools: z.number().int().describe("tools it exposes"),
|
|
79
|
+
transport: z.enum(["stdio", "http"]).describe("how okgate reaches it"),
|
|
80
|
+
error: z.string().optional().describe("why it is not connected")
|
|
81
|
+
})
|
|
82
|
+
).describe("the MCP servers this proxy fronts; empty means none are configured"),
|
|
83
|
+
run_id: z.string().nullable().describe(
|
|
84
|
+
"run the usage is for: the requested run_id, else the latest run in the log, else null"
|
|
85
|
+
),
|
|
86
|
+
caps: z.object({ per_run: counters, per_day: counters }).describe("limits from the policy; a counter that is absent has no cap"),
|
|
87
|
+
used: z.object({ per_run: counters, per_day: counters }).describe("what that run, and today, have used against the caps"),
|
|
88
|
+
remaining: z.record(
|
|
89
|
+
z.string(),
|
|
90
|
+
z.object({ per_run: z.number().optional(), per_day: z.number().optional() })
|
|
91
|
+
).describe("budget left per capped counter, per run and per day"),
|
|
92
|
+
kill_switch: z.object({
|
|
93
|
+
on: z.boolean().describe("true halts every tool call with KILLED"),
|
|
94
|
+
reason: z.string().optional().describe("why it was pulled"),
|
|
95
|
+
source: z.string().optional().describe("file, env or memory")
|
|
96
|
+
}).describe("the kill switch state"),
|
|
97
|
+
pending_approvals: z.number().int().describe("destructive calls waiting for a human decision"),
|
|
98
|
+
audit_log: z.object({
|
|
99
|
+
path: z.string().describe("absolute path of the hash-chained JSONL audit log"),
|
|
100
|
+
entries: z.number().int().describe("entries in it; 0 when it does not exist yet")
|
|
101
|
+
}).describe("where every tool call is recorded"),
|
|
102
|
+
next_step: z.string().describe("the one thing to do next, with the command when there is one")
|
|
103
|
+
});
|
|
104
|
+
var ReportOutput = z.object({
|
|
105
|
+
configured: z.boolean().describe("false when no policy file was found (okgate guards nothing)"),
|
|
106
|
+
audit_log_path: z.string().describe("absolute path of the audit log the report was built from"),
|
|
107
|
+
run_id: z.string().nullable().describe("the run reported on; null when the log has no runs"),
|
|
108
|
+
runs_in_log: z.number().int().describe("how many runs the audit log holds"),
|
|
109
|
+
tool_calls: z.number().int().describe("tool calls in this run"),
|
|
110
|
+
would_have: z.object({
|
|
111
|
+
creates: z.number().int(),
|
|
112
|
+
updates: z.number().int(),
|
|
113
|
+
deletes: z.number().int(),
|
|
114
|
+
sends: z.number().int(),
|
|
115
|
+
executes: z.number().int(),
|
|
116
|
+
spends: z.number().int(),
|
|
117
|
+
spend_usd: z.number()
|
|
118
|
+
}).describe("what dry-run mode faked: what the run would have done had the calls executed"),
|
|
119
|
+
did: z.object({
|
|
120
|
+
writes: z.number().int(),
|
|
121
|
+
deletes: z.number().int(),
|
|
122
|
+
sends: z.number().int(),
|
|
123
|
+
spend_usd: z.number()
|
|
124
|
+
}).describe("what actually executed upstream"),
|
|
125
|
+
halts: z.array(
|
|
126
|
+
z.object({
|
|
127
|
+
seq: z.number().int().describe("audit entry number"),
|
|
128
|
+
ts: z.string().describe("ISO time of the call"),
|
|
129
|
+
code: z.string().describe("CAP_EXCEEDED, LOOP_DETECTED, KILLED, APPROVAL_REQUIRED, \u2026"),
|
|
130
|
+
tool: z.string().describe("the tool that was stopped"),
|
|
131
|
+
cause: z.string().describe("why")
|
|
132
|
+
})
|
|
133
|
+
).describe("every call okgate stopped, in order"),
|
|
134
|
+
top_tools: z.array(z.object({ tool: z.string(), calls: z.number().int(), class: z.string() })).describe("the most-called tools with their class (read, write, spend)"),
|
|
135
|
+
markdown: z.string().describe("the same report `okgate report` prints, as Markdown")
|
|
136
|
+
});
|
|
137
|
+
var VerifyOutput = z.object({
|
|
138
|
+
configured: z.boolean().describe("false when no policy file was found (okgate guards nothing)"),
|
|
139
|
+
path: z.string().describe("absolute path of the audit log checked"),
|
|
140
|
+
exists: z.boolean().describe("false when no call has been recorded yet; an absent log verifies"),
|
|
141
|
+
ok: z.boolean().describe("true when every entry's hash and prev_hash recompute"),
|
|
142
|
+
entries: z.number().int().describe("entries checked"),
|
|
143
|
+
head_hash: z.string().nullable().describe(
|
|
144
|
+
"hash of the last entry; record it with the count to detect entries cut from the end"
|
|
145
|
+
),
|
|
146
|
+
broken_at: z.number().int().nullable().describe("seq of the first entry that does not verify"),
|
|
147
|
+
reason: z.string().nullable().describe("why verification failed")
|
|
148
|
+
});
|
|
149
|
+
function createOwnTools(runtime) {
|
|
150
|
+
const status = defineTool({
|
|
151
|
+
name: "okgate_get_status",
|
|
152
|
+
title: "okgate status",
|
|
153
|
+
description: "Report what okgate is guarding right now: the policy file it loaded (or that none was found), dry-run or enforce mode, each upstream MCP server and whether it connected, a run's cap usage and remaining budget, the kill switch, and pending approvals. Call it first to learn whether okgate is set up; next_step says what to do when it is not. For what a run did, use okgate_get_report; to check the log was not edited, okgate_verify_audit_log. Reads local files only and changes nothing.",
|
|
154
|
+
input: z.strictObject({ run_id: runIdInput }),
|
|
155
|
+
output: StatusOutput,
|
|
156
|
+
annotations: { title: "okgate status", ...READ_ONLY },
|
|
157
|
+
async handler({ run_id }) {
|
|
158
|
+
const { loaded, guard } = runtime;
|
|
159
|
+
const audit = readAudit(loaded.auditPath);
|
|
160
|
+
if (audit.error) throw unreadable(loaded.auditPath, audit.error);
|
|
161
|
+
const runId = run_id ?? latestRunId(audit.entries) ?? null;
|
|
162
|
+
const state = await guard.status(runId ?? "(none yet)");
|
|
163
|
+
const pending = await guard.approvals.list("pending");
|
|
164
|
+
return {
|
|
165
|
+
configured: runtime.configured,
|
|
166
|
+
policy_path: loaded.path,
|
|
167
|
+
mode: runtime.policy.mode,
|
|
168
|
+
upstreams: runtime.upstreamStatus(),
|
|
169
|
+
run_id: runId,
|
|
170
|
+
caps: { per_run: numbers(state.caps.per_run), per_day: numbers(state.caps.per_day) },
|
|
171
|
+
used: state.usage,
|
|
172
|
+
remaining: state.remaining,
|
|
173
|
+
kill_switch: {
|
|
174
|
+
on: state.killed.killed,
|
|
175
|
+
...state.killed.reason ? { reason: state.killed.reason } : {},
|
|
176
|
+
...state.killed.source ? { source: state.killed.source } : {}
|
|
177
|
+
},
|
|
178
|
+
pending_approvals: pending.length,
|
|
179
|
+
audit_log: { path: loaded.auditPath, entries: audit.entries.length },
|
|
180
|
+
next_step: nextStep(runtime)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
const report = defineTool({
|
|
185
|
+
name: "okgate_get_report",
|
|
186
|
+
title: "okgate run report",
|
|
187
|
+
description: "Summarize one run from okgate's audit log: how many tool calls, what dry-run mode faked (records it would have created, updated or deleted, messages sent, dollars spent), what really ran upstream, and every call okgate halted with its code and cause. Defaults to the most recent run. Use it after an agent run, before switching a policy from dry-run to enforce; for live caps and the kill switch use okgate_get_status. Read-only: reads the audit file and does not record this call.",
|
|
188
|
+
input: z.strictObject({ run_id: runIdInput }),
|
|
189
|
+
output: ReportOutput,
|
|
190
|
+
annotations: { title: "okgate run report", ...READ_ONLY },
|
|
191
|
+
handler({ run_id }) {
|
|
192
|
+
const { loaded } = runtime;
|
|
193
|
+
const audit = readAudit(loaded.auditPath);
|
|
194
|
+
if (audit.error) throw unreadable(loaded.auditPath, audit.error);
|
|
195
|
+
const runs = listRuns(audit.entries);
|
|
196
|
+
if (run_id && !runs.some((r) => r.runId === run_id)) {
|
|
197
|
+
throw toolError({
|
|
198
|
+
code: "NOT_FOUND",
|
|
199
|
+
cause: `no run "${run_id}" in the audit log at ${loaded.auditPath}`,
|
|
200
|
+
fix: `call again without run_id for the latest run${runs.length ? `; runs in the log: ${runs.slice(0, 5).map((r) => r.runId).join(", ")}` : "; the log has no runs yet"}`,
|
|
201
|
+
retryable: false
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const runId = run_id ?? latestRunId(audit.entries) ?? null;
|
|
205
|
+
const built = buildReport(audit.entries, { runId: runId ?? void 0 });
|
|
206
|
+
const markdown = runId ? renderReportMarkdown(built) : `# okgate report
|
|
207
|
+
|
|
208
|
+
No runs recorded in ${loaded.auditPath} yet. ${nextStep(runtime)}`;
|
|
209
|
+
return {
|
|
210
|
+
configured: runtime.configured,
|
|
211
|
+
audit_log_path: loaded.auditPath,
|
|
212
|
+
run_id: runId,
|
|
213
|
+
runs_in_log: runs.length,
|
|
214
|
+
tool_calls: runId ? audit.entries.filter((e) => e.run_id === runId).length : 0,
|
|
215
|
+
would_have: {
|
|
216
|
+
creates: built.wouldHave.creates,
|
|
217
|
+
updates: built.wouldHave.updates,
|
|
218
|
+
deletes: built.wouldHave.deletes,
|
|
219
|
+
sends: built.wouldHave.sends,
|
|
220
|
+
executes: built.wouldHave.executes,
|
|
221
|
+
spends: built.wouldHave.spends,
|
|
222
|
+
spend_usd: built.wouldHave.spendUsd
|
|
223
|
+
},
|
|
224
|
+
did: {
|
|
225
|
+
writes: built.did.writes,
|
|
226
|
+
deletes: built.did.deletes,
|
|
227
|
+
sends: built.did.sends,
|
|
228
|
+
spend_usd: built.did.spendUsd
|
|
229
|
+
},
|
|
230
|
+
halts: built.halts,
|
|
231
|
+
top_tools: built.topTools,
|
|
232
|
+
markdown
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
const verify = defineTool({
|
|
237
|
+
name: "okgate_verify_audit_log",
|
|
238
|
+
title: "Verify okgate audit log",
|
|
239
|
+
description: "Recompute the hash chain of okgate's audit log (JSONL, one entry per tool call) and say whether any entry was edited, removed from the middle or reordered, naming the first broken entry. Returns the entry count and head hash; record both elsewhere, because entries cut from the end of the file still verify. Use it before trusting okgate_get_report. Takes no arguments: it checks the log the loaded policy names. Read-only; a log that does not exist yet verifies as empty.",
|
|
240
|
+
input: z.strictObject({}),
|
|
241
|
+
output: VerifyOutput,
|
|
242
|
+
annotations: { title: "Verify okgate audit log", ...READ_ONLY },
|
|
243
|
+
async handler() {
|
|
244
|
+
const { loaded } = runtime;
|
|
245
|
+
const audit = readAudit(loaded.auditPath);
|
|
246
|
+
if (audit.error) {
|
|
247
|
+
return {
|
|
248
|
+
configured: runtime.configured,
|
|
249
|
+
path: loaded.auditPath,
|
|
250
|
+
exists: true,
|
|
251
|
+
ok: false,
|
|
252
|
+
entries: 0,
|
|
253
|
+
head_hash: null,
|
|
254
|
+
broken_at: null,
|
|
255
|
+
reason: audit.error
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const result = await verifyChain(audit.entries);
|
|
259
|
+
return {
|
|
260
|
+
configured: runtime.configured,
|
|
261
|
+
path: loaded.auditPath,
|
|
262
|
+
exists: audit.exists,
|
|
263
|
+
ok: result.ok,
|
|
264
|
+
entries: result.entries,
|
|
265
|
+
// an empty chain's head is the genesis hash, which is not an entry anyone can record
|
|
266
|
+
head_hash: result.entries > 0 ? result.head ?? null : null,
|
|
267
|
+
broken_at: result.brokenAt ?? null,
|
|
268
|
+
reason: result.reason ?? null
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
return [status, report, verify];
|
|
273
|
+
}
|
|
274
|
+
function ownToolsActive(runtime) {
|
|
275
|
+
return runtime.upstreamStatus().length === 0;
|
|
276
|
+
}
|
|
277
|
+
function ownToolList(tools) {
|
|
278
|
+
return listToolManifest(tools);
|
|
279
|
+
}
|
|
280
|
+
async function callOwnTool(tools, name, args, requestId, now = /* @__PURE__ */ new Date()) {
|
|
281
|
+
const resolved = resolveToolName(name, (n) => tools.some((t) => t.name === n), now);
|
|
282
|
+
if (resolved.kind === "expired")
|
|
283
|
+
return {
|
|
284
|
+
isError: true,
|
|
285
|
+
content: [{ type: "text", text: JSON.stringify(resolved.error) }],
|
|
286
|
+
_meta: { httpStatus: 404 }
|
|
287
|
+
};
|
|
288
|
+
const tool = tools.find((t) => t.name === resolved.name);
|
|
289
|
+
if (!tool) return void 0;
|
|
290
|
+
const result = await tool.invoke(args, createToolContext({ requestId }));
|
|
291
|
+
if (resolved.kind === "renamed")
|
|
292
|
+
return { ...result, _meta: { ...result._meta, ...resolved.meta } };
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/proxy/server.ts
|
|
297
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
298
|
+
import {
|
|
299
|
+
CallToolRequestSchema,
|
|
300
|
+
CompleteRequestSchema,
|
|
301
|
+
GetPromptRequestSchema,
|
|
302
|
+
ListPromptsRequestSchema,
|
|
303
|
+
ListResourceTemplatesRequestSchema,
|
|
304
|
+
ListResourcesRequestSchema,
|
|
305
|
+
ListToolsRequestSchema,
|
|
306
|
+
ReadResourceRequestSchema,
|
|
307
|
+
SetLevelRequestSchema,
|
|
308
|
+
SubscribeRequestSchema,
|
|
309
|
+
UnsubscribeRequestSchema
|
|
310
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
311
|
+
import {
|
|
312
|
+
keyFromHeaders,
|
|
313
|
+
resolveAgent
|
|
314
|
+
} from "@agentwares/agentguard-core";
|
|
315
|
+
var PROXY_VERSION = version();
|
|
316
|
+
function headerValue(extra, name) {
|
|
317
|
+
const raw = extra.requestInfo?.headers?.[name] ?? extra.requestInfo?.headers?.[name.toLowerCase()];
|
|
318
|
+
if (Array.isArray(raw)) return raw[0];
|
|
319
|
+
return typeof raw === "string" ? raw : void 0;
|
|
320
|
+
}
|
|
321
|
+
function headersOf(extra) {
|
|
322
|
+
const out = {};
|
|
323
|
+
for (const [k, v] of Object.entries(extra.requestInfo?.headers ?? {}))
|
|
324
|
+
out[k.toLowerCase()] = Array.isArray(v) ? v[0] : v;
|
|
325
|
+
return out;
|
|
326
|
+
}
|
|
327
|
+
function runIdFor(extra, meta, fallback) {
|
|
328
|
+
const header = headerValue(extra, "x-run-id");
|
|
329
|
+
if (header && header.trim()) return header.trim().slice(0, 128);
|
|
330
|
+
const fromMeta = meta?.runId ?? meta?.["okgate/runId"] ?? meta?.["agentguard/runId"] ?? meta?.run_id;
|
|
331
|
+
if (typeof fromMeta === "string" && fromMeta.trim()) return fromMeta.trim().slice(0, 128);
|
|
332
|
+
if (extra.sessionId) return `sess_${extra.sessionId.slice(0, 12)}`;
|
|
333
|
+
return fallback;
|
|
334
|
+
}
|
|
335
|
+
function toCallToolResult(result, tool) {
|
|
336
|
+
const verdict = {
|
|
337
|
+
class: result.decision.classification.class,
|
|
338
|
+
verb: result.decision.classification.verb,
|
|
339
|
+
mode: result.decision.mode,
|
|
340
|
+
outcome: result.outcome,
|
|
341
|
+
dryRun: result.faked,
|
|
342
|
+
seq: result.entry.seq,
|
|
343
|
+
run_id: result.entry.run_id
|
|
344
|
+
};
|
|
345
|
+
const meta = { okgate: verdict, agentguard: verdict };
|
|
346
|
+
const hasOutputSchema = Boolean(tool?.tool.outputSchema);
|
|
347
|
+
if (result.error) {
|
|
348
|
+
const upstream2 = result.value;
|
|
349
|
+
if (upstream2 && upstream2.content) {
|
|
350
|
+
return { ...upstream2, isError: true, _meta: { ...upstream2._meta ?? {}, ...meta } };
|
|
351
|
+
}
|
|
352
|
+
const body = result.error;
|
|
353
|
+
return {
|
|
354
|
+
isError: true,
|
|
355
|
+
content: [{ type: "text", text: JSON.stringify(body) }],
|
|
356
|
+
...hasOutputSchema ? {} : { structuredContent: body },
|
|
357
|
+
_meta: { ...meta, httpStatus: httpStatusFor(body.code) }
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (result.faked) {
|
|
361
|
+
const value = result.value;
|
|
362
|
+
const isObject = value !== null && typeof value === "object" && !Array.isArray(value);
|
|
363
|
+
return {
|
|
364
|
+
content: [
|
|
365
|
+
{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }
|
|
366
|
+
],
|
|
367
|
+
...isObject ? { structuredContent: value } : {},
|
|
368
|
+
_meta: meta
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
const upstream = result.value;
|
|
372
|
+
return { ...upstream, _meta: { ...upstream._meta ?? {}, ...meta } };
|
|
373
|
+
}
|
|
374
|
+
function httpStatusFor(code) {
|
|
375
|
+
switch (code) {
|
|
376
|
+
case "KILLED":
|
|
377
|
+
case "TOOL_DENIED":
|
|
378
|
+
case "APPROVAL_DENIED":
|
|
379
|
+
return 403;
|
|
380
|
+
case "APPROVAL_REQUIRED":
|
|
381
|
+
return 202;
|
|
382
|
+
case "CAP_EXCEEDED":
|
|
383
|
+
case "LOOP_DETECTED":
|
|
384
|
+
return 429;
|
|
385
|
+
case "UNKNOWN_TOOL":
|
|
386
|
+
return 404;
|
|
387
|
+
case "UPSTREAM_ERROR":
|
|
388
|
+
return 502;
|
|
389
|
+
default:
|
|
390
|
+
return 500;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function annotateTool(t, mode) {
|
|
394
|
+
return {
|
|
395
|
+
...t.tool,
|
|
396
|
+
name: t.name,
|
|
397
|
+
_meta: {
|
|
398
|
+
...t.tool._meta ?? {},
|
|
399
|
+
okgate: {
|
|
400
|
+
upstream: t.upstream,
|
|
401
|
+
class: t.classification.class,
|
|
402
|
+
verb: t.classification.verb,
|
|
403
|
+
mode
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function killedError(state) {
|
|
409
|
+
return Object.assign(
|
|
410
|
+
new Error(`okgate kill switch is on (${state.source ?? "?"}: ${state.reason ?? ""})`),
|
|
411
|
+
{ code: -32e3 }
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
function serverInstructions(runtime) {
|
|
415
|
+
if (ownToolsActive(runtime))
|
|
416
|
+
return `okgate is an MCP policy proxy, and this one fronts no upstream MCP servers${runtime.configured ? "" : ` (no policy file at ${runtime.loaded.path})`}, so it serves only its own read-only tools: okgate_get_status (what it guards, caps, kill switch), okgate_get_report (what a run did or would have done) and okgate_verify_audit_log. To guard the user's MCP servers, run \`${INIT_COMMAND}\` in the project directory and restart the client.`;
|
|
417
|
+
return `Tool calls pass through okgate (${runtime.policy.mode} mode). Errors carry { code, cause, fix, retryable }; follow "fix". CAP_EXCEEDED, LOOP_DETECTED and KILLED are not retryable: stop and report to the user. APPROVAL_REQUIRED: relay the approval command to the user, then retry the identical call once.`;
|
|
418
|
+
}
|
|
419
|
+
function createDownstreamServer(runtime, opts = {}) {
|
|
420
|
+
const server = new McpServer(
|
|
421
|
+
{ name: "okgate", version: PROXY_VERSION, title: "okgate policy proxy" },
|
|
422
|
+
{
|
|
423
|
+
capabilities: {
|
|
424
|
+
tools: { listChanged: true },
|
|
425
|
+
resources: { subscribe: true, listChanged: true },
|
|
426
|
+
prompts: { listChanged: true },
|
|
427
|
+
logging: {},
|
|
428
|
+
completions: {}
|
|
429
|
+
},
|
|
430
|
+
instructions: serverInstructions(runtime)
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
const ownTools = createOwnTools(runtime);
|
|
434
|
+
const session = {
|
|
435
|
+
id: crypto.randomUUID(),
|
|
436
|
+
server,
|
|
437
|
+
lastActive: Date.now(),
|
|
438
|
+
inflight: /* @__PURE__ */ new Map()
|
|
439
|
+
};
|
|
440
|
+
runtime.attach(session);
|
|
441
|
+
const previousOnClose = server.server.onclose;
|
|
442
|
+
server.server.onclose = () => {
|
|
443
|
+
previousOnClose?.();
|
|
444
|
+
runtime.detach(session);
|
|
445
|
+
};
|
|
446
|
+
const touch = () => {
|
|
447
|
+
session.lastActive = Date.now();
|
|
448
|
+
};
|
|
449
|
+
const agentFor = async (extra) => {
|
|
450
|
+
const key = keyFromHeaders(headersOf(extra));
|
|
451
|
+
if (!key) return { agent: void 0 };
|
|
452
|
+
const resolved = await resolveAgent(runtime.policy, { key });
|
|
453
|
+
if (resolved.error) {
|
|
454
|
+
return {
|
|
455
|
+
agent: null,
|
|
456
|
+
error: {
|
|
457
|
+
code: "TOOL_DENIED",
|
|
458
|
+
cause: "the agent key in Authorization / X-Agentguard-Key is not in okgate.yaml",
|
|
459
|
+
fix: "use a key created with `okgate key create <agent>`, or omit the key to use the global policy",
|
|
460
|
+
retryable: false
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
return { agent: resolved.agent };
|
|
465
|
+
};
|
|
466
|
+
const low = server.server;
|
|
467
|
+
low.setRequestHandler(ListToolsRequestSchema, async (_req, extra) => {
|
|
468
|
+
touch();
|
|
469
|
+
const { agent } = await agentFor(extra);
|
|
470
|
+
const scope = agent === void 0 ? runtime.agent : agent;
|
|
471
|
+
if (ownToolsActive(runtime)) return { tools: ownToolList(ownTools) };
|
|
472
|
+
return {
|
|
473
|
+
tools: runtime.listTools(scope).map((t) => annotateTool(t, scope?.mode ?? runtime.policy.mode))
|
|
474
|
+
};
|
|
475
|
+
});
|
|
476
|
+
low.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
|
|
477
|
+
touch();
|
|
478
|
+
if (ownToolsActive(runtime)) {
|
|
479
|
+
const own = await callOwnTool(
|
|
480
|
+
ownTools,
|
|
481
|
+
req.params.name,
|
|
482
|
+
req.params.arguments ?? {},
|
|
483
|
+
String(extra.requestId)
|
|
484
|
+
);
|
|
485
|
+
if (own) return own;
|
|
486
|
+
}
|
|
487
|
+
const { agent, error } = await agentFor(extra);
|
|
488
|
+
const runId = runIdFor(extra, req.params._meta, opts.defaultRunId ?? runtime.defaultRunId);
|
|
489
|
+
if (error) {
|
|
490
|
+
return {
|
|
491
|
+
isError: true,
|
|
492
|
+
content: [{ type: "text", text: JSON.stringify(error) }],
|
|
493
|
+
structuredContent: error,
|
|
494
|
+
_meta: { httpStatus: 401 }
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
const progressToken = req.params._meta?.progressToken;
|
|
498
|
+
const { result, tool } = await runtime.callTool(req.params.name, req.params.arguments ?? {}, {
|
|
499
|
+
runId,
|
|
500
|
+
agent,
|
|
501
|
+
sessionId: extra.sessionId ?? session.id,
|
|
502
|
+
session,
|
|
503
|
+
requestId: extra.requestId,
|
|
504
|
+
onprogress: progressToken === void 0 ? void 0 : (p) => {
|
|
505
|
+
void extra.sendNotification({
|
|
506
|
+
method: "notifications/progress",
|
|
507
|
+
params: { progressToken, ...p }
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
return toCallToolResult(result, tool);
|
|
512
|
+
});
|
|
513
|
+
const guardRead = async () => {
|
|
514
|
+
const state = await runtime.guard.killState();
|
|
515
|
+
if (state.killed) throw killedError(state);
|
|
516
|
+
};
|
|
517
|
+
low.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
518
|
+
touch();
|
|
519
|
+
return runtime.listResources();
|
|
520
|
+
});
|
|
521
|
+
low.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
522
|
+
touch();
|
|
523
|
+
return runtime.listResourceTemplates();
|
|
524
|
+
});
|
|
525
|
+
low.setRequestHandler(ReadResourceRequestSchema, async (req) => {
|
|
526
|
+
touch();
|
|
527
|
+
await guardRead();
|
|
528
|
+
return runtime.readResource(req.params.uri);
|
|
529
|
+
});
|
|
530
|
+
low.setRequestHandler(SubscribeRequestSchema, async (req) => {
|
|
531
|
+
await runtime.subscribe(req.params.uri, true);
|
|
532
|
+
return {};
|
|
533
|
+
});
|
|
534
|
+
low.setRequestHandler(UnsubscribeRequestSchema, async (req) => {
|
|
535
|
+
await runtime.subscribe(req.params.uri, false);
|
|
536
|
+
return {};
|
|
537
|
+
});
|
|
538
|
+
low.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
539
|
+
touch();
|
|
540
|
+
return runtime.listPrompts();
|
|
541
|
+
});
|
|
542
|
+
low.setRequestHandler(GetPromptRequestSchema, async (req) => {
|
|
543
|
+
touch();
|
|
544
|
+
await guardRead();
|
|
545
|
+
return runtime.getPrompt(req.params.name, req.params.arguments);
|
|
546
|
+
});
|
|
547
|
+
low.setRequestHandler(CompleteRequestSchema, async (req) => {
|
|
548
|
+
touch();
|
|
549
|
+
return runtime.complete(req.params);
|
|
550
|
+
});
|
|
551
|
+
low.setRequestHandler(SetLevelRequestSchema, async (req) => {
|
|
552
|
+
await runtime.setLevel(req.params.level);
|
|
553
|
+
return {};
|
|
554
|
+
});
|
|
555
|
+
return server;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// src/proxy/http.ts
|
|
559
|
+
import { writeFileSync, unlinkSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
560
|
+
import { createServer } from "http";
|
|
561
|
+
import { killSwitchFor } from "@agentwares/agentguard-core/node";
|
|
562
|
+
function json(res, status, body) {
|
|
563
|
+
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
564
|
+
res.end(JSON.stringify(body));
|
|
565
|
+
}
|
|
566
|
+
function html(res, status, body) {
|
|
567
|
+
res.writeHead(status, {
|
|
568
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
569
|
+
"Cache-Control": "no-store"
|
|
570
|
+
});
|
|
571
|
+
res.end(
|
|
572
|
+
`<!doctype html><meta name="viewport" content="width=device-width"><body style="font:16px system-ui;padding:2rem;max-width:40rem">${body}</body>`
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
async function readBody(req) {
|
|
576
|
+
if (req.method === "GET" || req.method === "HEAD") return void 0;
|
|
577
|
+
const chunks = [];
|
|
578
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
579
|
+
return new Uint8Array(Buffer.concat(chunks));
|
|
580
|
+
}
|
|
581
|
+
function isLoopback(host) {
|
|
582
|
+
return host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "[::1]";
|
|
583
|
+
}
|
|
584
|
+
async function startHttpProxy(opts) {
|
|
585
|
+
const { runtime } = opts;
|
|
586
|
+
const host = opts.host ?? "127.0.0.1";
|
|
587
|
+
const path = opts.path ?? "/mcp";
|
|
588
|
+
const token = opts.token ?? crypto.randomUUID().replace(/-/g, "");
|
|
589
|
+
const kill = killSwitchFor(runtime.loaded);
|
|
590
|
+
const holder = {};
|
|
591
|
+
const server = createServer((req, res) => {
|
|
592
|
+
void (async () => {
|
|
593
|
+
const url2 = new URL(req.url ?? "/", `http://${req.headers.host ?? host}`);
|
|
594
|
+
const authorized = () => {
|
|
595
|
+
const q = url2.searchParams.get("token");
|
|
596
|
+
const h = req.headers.authorization?.replace(/^Bearer\s+/i, "");
|
|
597
|
+
return q === token || h === token;
|
|
598
|
+
};
|
|
599
|
+
if (url2.pathname === path && holder.mcp) {
|
|
600
|
+
const headers = new Headers();
|
|
601
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
602
|
+
if (Array.isArray(v)) for (const item of v) headers.append(k, item);
|
|
603
|
+
else if (typeof v === "string") headers.set(k, v);
|
|
604
|
+
}
|
|
605
|
+
const controller = new AbortController();
|
|
606
|
+
res.on("close", () => {
|
|
607
|
+
if (!res.writableFinished) controller.abort();
|
|
608
|
+
});
|
|
609
|
+
const response = await holder.mcp(
|
|
610
|
+
new Request(url2, {
|
|
611
|
+
method: req.method ?? "GET",
|
|
612
|
+
headers,
|
|
613
|
+
body: await readBody(req),
|
|
614
|
+
signal: controller.signal
|
|
615
|
+
})
|
|
616
|
+
);
|
|
617
|
+
const out = {};
|
|
618
|
+
response.headers.forEach((v, k) => {
|
|
619
|
+
out[k] = v;
|
|
620
|
+
});
|
|
621
|
+
res.writeHead(response.status, out);
|
|
622
|
+
if (!response.body) return res.end();
|
|
623
|
+
const reader = response.body.getReader();
|
|
624
|
+
try {
|
|
625
|
+
for (; ; ) {
|
|
626
|
+
const { done, value } = await reader.read();
|
|
627
|
+
if (done) break;
|
|
628
|
+
res.write(value);
|
|
629
|
+
}
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
return res.end();
|
|
633
|
+
}
|
|
634
|
+
if (url2.pathname === "/health") {
|
|
635
|
+
const killed = await runtime.guard.killState();
|
|
636
|
+
return json(res, 200, {
|
|
637
|
+
ok: true,
|
|
638
|
+
name: "okgate",
|
|
639
|
+
version: PROXY_VERSION,
|
|
640
|
+
mode: runtime.policy.mode,
|
|
641
|
+
killed: killed.killed,
|
|
642
|
+
upstreams: runtime.upstreamStatus(),
|
|
643
|
+
tools: runtime.listTools().length,
|
|
644
|
+
mcp: `${url2.origin}${path}`
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
if (url2.pathname === "/status") {
|
|
648
|
+
if (!authorized()) return json(res, 401, unauthorized());
|
|
649
|
+
const runId = url2.searchParams.get("run") ?? runtime.defaultRunId;
|
|
650
|
+
return json(res, 200, { runId, ...await runtime.guard.status(runId) });
|
|
651
|
+
}
|
|
652
|
+
if (url2.pathname === "/kill" && req.method === "POST") {
|
|
653
|
+
if (!authorized()) return json(res, 401, unauthorized());
|
|
654
|
+
const body = await readBody(req);
|
|
655
|
+
let reason = "killed via HTTP";
|
|
656
|
+
try {
|
|
657
|
+
const parsed = JSON.parse(Buffer.from(body ?? []).toString("utf8") || "{}");
|
|
658
|
+
if (parsed.reason) reason = String(parsed.reason).slice(0, 200);
|
|
659
|
+
} catch {
|
|
660
|
+
}
|
|
661
|
+
kill.kill(reason);
|
|
662
|
+
await runtime.guard.halt(reason);
|
|
663
|
+
return json(res, 200, { killed: true, reason });
|
|
664
|
+
}
|
|
665
|
+
if (url2.pathname === "/resume" && req.method === "POST") {
|
|
666
|
+
if (!authorized()) return json(res, 401, unauthorized());
|
|
667
|
+
kill.resume();
|
|
668
|
+
await runtime.guard.resume();
|
|
669
|
+
return json(res, 200, { killed: false });
|
|
670
|
+
}
|
|
671
|
+
const decision = /^\/(approve|deny)\/([A-Za-z0-9_-]+)$/.exec(url2.pathname);
|
|
672
|
+
if (decision) {
|
|
673
|
+
if (!authorized()) return json(res, 401, unauthorized());
|
|
674
|
+
const [, action, id] = decision;
|
|
675
|
+
const record = await runtime.guard.decide(
|
|
676
|
+
id,
|
|
677
|
+
action === "approve" ? "approved" : "denied",
|
|
678
|
+
"http"
|
|
679
|
+
);
|
|
680
|
+
if (!record)
|
|
681
|
+
return json(res, 404, {
|
|
682
|
+
code: "NOT_FOUND",
|
|
683
|
+
cause: `no approval ${id}`,
|
|
684
|
+
fix: "list pending approvals with `okgate approvals`",
|
|
685
|
+
retryable: false
|
|
686
|
+
});
|
|
687
|
+
if (req.headers.accept?.includes("text/html")) {
|
|
688
|
+
return html(
|
|
689
|
+
res,
|
|
690
|
+
200,
|
|
691
|
+
`<h1>${record.status === "approved" ? "Approved" : record.status === "denied" ? "Denied" : record.status}</h1><p><code>${record.tool}</code> \u2014 approval <code>${record.id}</code>.</p><p>The agent may now retry the call once.</p>`
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
return json(res, 200, record);
|
|
695
|
+
}
|
|
696
|
+
if (url2.pathname === "/approvals") {
|
|
697
|
+
if (!authorized()) return json(res, 401, unauthorized());
|
|
698
|
+
return json(res, 200, {
|
|
699
|
+
approvals: await runtime.guard.approvals.list(
|
|
700
|
+
url2.searchParams.get("all") ? void 0 : "pending"
|
|
701
|
+
)
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
return json(res, 404, {
|
|
705
|
+
code: "NOT_FOUND",
|
|
706
|
+
cause: `no route for ${url2.pathname}`,
|
|
707
|
+
fix: `POST JSON-RPC to ${path}; GET /health`,
|
|
708
|
+
retryable: false
|
|
709
|
+
});
|
|
710
|
+
})().catch((err) => {
|
|
711
|
+
if (!res.headersSent)
|
|
712
|
+
json(res, 500, {
|
|
713
|
+
code: "INTERNAL",
|
|
714
|
+
cause: err instanceof Error ? err.message : String(err),
|
|
715
|
+
fix: "retry; if it persists, report the request",
|
|
716
|
+
retryable: false
|
|
717
|
+
});
|
|
718
|
+
else res.end();
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
await new Promise((resolve, reject) => {
|
|
722
|
+
server.once("error", reject);
|
|
723
|
+
server.listen(opts.port ?? 8788, host, () => resolve());
|
|
724
|
+
});
|
|
725
|
+
const address = server.address();
|
|
726
|
+
const port = typeof address === "object" && address ? address.port : opts.port ?? 8788;
|
|
727
|
+
holder.mcp = createHttpHandler(() => createDownstreamServer(runtime), {
|
|
728
|
+
sessions: true,
|
|
729
|
+
jsonResponse: false,
|
|
730
|
+
cors: true,
|
|
731
|
+
allowedHosts: isLoopback(host) ? localhostHosts(port) : void 0,
|
|
732
|
+
enableDnsRebindingProtection: isLoopback(host)
|
|
733
|
+
});
|
|
734
|
+
const baseUrl = `http://${host}:${port}`;
|
|
735
|
+
const url = `${baseUrl}${path}`;
|
|
736
|
+
const regFile = registrationPath(runtime.loaded.stateDir);
|
|
737
|
+
if (opts.registerFile !== false) {
|
|
738
|
+
mkdirSync(runtime.loaded.stateDir, { recursive: true });
|
|
739
|
+
const reg = {
|
|
740
|
+
url,
|
|
741
|
+
baseUrl,
|
|
742
|
+
token,
|
|
743
|
+
pid: process.pid,
|
|
744
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
745
|
+
};
|
|
746
|
+
writeFileSync(regFile, JSON.stringify(reg, null, 2));
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
url,
|
|
750
|
+
port,
|
|
751
|
+
token,
|
|
752
|
+
close: () => new Promise((resolve, reject) => {
|
|
753
|
+
if (opts.registerFile !== false && existsSync2(regFile)) {
|
|
754
|
+
try {
|
|
755
|
+
unlinkSync(regFile);
|
|
756
|
+
} catch {
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
server.closeAllConnections();
|
|
760
|
+
server.close((err) => err ? reject(err) : resolve());
|
|
761
|
+
})
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
function unauthorized() {
|
|
765
|
+
return {
|
|
766
|
+
code: "UNAUTHORIZED",
|
|
767
|
+
cause: "missing or wrong control token",
|
|
768
|
+
fix: "pass ?token=<token> or Authorization: Bearer <token> from <state>/http.json",
|
|
769
|
+
retryable: false
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
export {
|
|
774
|
+
INIT_COMMAND,
|
|
775
|
+
runIdFor,
|
|
776
|
+
toCallToolResult,
|
|
777
|
+
createDownstreamServer,
|
|
778
|
+
startHttpProxy
|
|
779
|
+
};
|
|
780
|
+
//# sourceMappingURL=chunk-JSELR4SG.js.map
|