@agentwares/agentguard 0.1.2 → 0.1.3
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/LICENSE +21 -0
- package/README.md +35 -17
- package/dist/cli.js +303 -150
- package/dist/cli.js.map +1 -1
- package/dist/fixtures/crm-server.js +53 -24
- package/dist/fixtures/crm-server.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +202 -49
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -482,7 +482,7 @@ interface DemoOptions {
|
|
|
482
482
|
/** Drive a connected client through the scripted scenario. Returns every step's outcome. */
|
|
483
483
|
declare function runDemoAgent(client: Client, opts?: DemoOptions): Promise<DemoStep[]>;
|
|
484
484
|
|
|
485
|
-
declare const HELP = "agentguard \u2014 MCP policy proxy for agents that touch production\n\n npx @agentwares/agentguard init read your MCP config, write agentguard.yaml (dry-run), route every server through the proxy\n agentguard report [--run id|--all] what this run did / would have destroyed / spent, where it was halted\n agentguard diff [--run id] record-by-record mutation diff from a dry run\n agentguard verify [audit.jsonl] prove the hash-chained audit log was not edited\n\n agentguard proxy [--http --port 8788] [--agent name] [--run-id id] [--mode dry-run|enforce]\n agentguard tools [--json] the tools your agent will see, with class and why\n agentguard status [--run id] counters vs caps, kill state, pending approvals\n\n agentguard kill [reason] halt every run now (file + running HTTP proxy); agentguard resume\n agentguard approvals [--all] pending approvals; agentguard approve <id> | deny <id> [--note text]\n agentguard key create <agent> [--allow 'crm_get_*']... [--deny p] [--writes n] [--spend n] [--mode m]\n agentguard key list | revoke <agent>\n agentguard permission-diff [--base ref] [--head ref] [--paths a,b] [--format markdown|json] [--fail-on-widen]\n\nOptions: --config <agentguard.yaml> (or AGENTGUARD_CONFIG), --json, --help, --version\nDocs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli";
|
|
485
|
+
declare const HELP = "agentguard \u2014 MCP policy proxy for agents that touch production\n\n npx @agentwares/agentguard init read your MCP config, write agentguard.yaml (dry-run), route every server through the proxy\n agentguard report [--run id|--all] what this run did / would have destroyed / spent, where it was halted\n agentguard diff [--run id] record-by-record mutation diff from a dry run\n agentguard verify [audit.jsonl] prove the hash-chained audit log was not edited\n agentguard connect <key> [--write] point this machine at your hosted proxy (paid tiers)\n\n agentguard proxy [--http --port 8788] [--agent name] [--run-id id] [--mode dry-run|enforce]\n agentguard tools [--json] the tools your agent will see, with class and why\n agentguard status [--run id] counters vs caps, kill state, pending approvals\n\n agentguard kill [reason] halt every run now (file + running HTTP proxy); agentguard resume\n agentguard approvals [--all] pending approvals; agentguard approve <id> | deny <id> [--note text]\n agentguard key create <agent> [--allow 'crm_get_*']... [--deny p] [--writes n] [--spend n] [--mode m]\n agentguard key list | revoke <agent>\n agentguard permission-diff [--base ref] [--head ref] [--paths a,b] [--format markdown|json] [--fail-on-widen]\n\nOptions: --config <agentguard.yaml> (or AGENTGUARD_CONFIG), --json, --help, --version\nDocs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli";
|
|
486
486
|
declare function main(argv: readonly string[], io?: Io): Promise<number>;
|
|
487
487
|
|
|
488
488
|
export { type CallContext, type ClientConfigFile, type CrmState, DEFAULT_PERMISSION_PATHS, type DemoOptions, type DemoStep, type DownstreamOptions, type DownstreamSession, type ExposedTool, type FileGuard, HELP, type HttpProxy, type HttpProxyOptions, type HttpRegistration, type Io, type PermissionDiffResult, type ProbeResult, type ProbedTool, ProxyRuntime, type RuntimeOptions, type ServerEntry, type UpstreamStatus, approvalSlackMessage, candidateConfigs, createAlerter, createCrmMcpServer, createDownstreamServer, createFileGuard, crmTools, detectClientConfigs, haltSlackMessage, loadPolicyFor, main, probeUpstreams, proxyEntry, readRegistration, readServers, registrationPath, renderStarterPolicy, restoreClientConfig, rewriteClientConfig, runDemoAgent, runIdFor, runPermissionDiff, seedState, serversToUpstreams, startHttpProxy, toCallToolResult };
|
package/dist/index.js
CHANGED
|
@@ -806,6 +806,21 @@ var require_dist = __commonJS({
|
|
|
806
806
|
|
|
807
807
|
// src/proxy/runtime.ts
|
|
808
808
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
809
|
+
|
|
810
|
+
// src/version.ts
|
|
811
|
+
import { readFileSync } from "fs";
|
|
812
|
+
var cached;
|
|
813
|
+
function version() {
|
|
814
|
+
if (cached !== void 0) return cached;
|
|
815
|
+
try {
|
|
816
|
+
cached = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
817
|
+
} catch {
|
|
818
|
+
cached = "0.0.0";
|
|
819
|
+
}
|
|
820
|
+
return cached;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/proxy/runtime.ts
|
|
809
824
|
import {
|
|
810
825
|
getDefaultEnvironment,
|
|
811
826
|
StdioClientTransport
|
|
@@ -886,7 +901,7 @@ var ProxyRuntime = class {
|
|
|
886
901
|
}
|
|
887
902
|
async connect(upstream) {
|
|
888
903
|
const client = new Client(
|
|
889
|
-
{ name: "agentguard", version:
|
|
904
|
+
{ name: "agentguard", version: version() },
|
|
890
905
|
{ capabilities: { sampling: {}, elicitation: {} } }
|
|
891
906
|
);
|
|
892
907
|
const conn = {
|
|
@@ -1331,7 +1346,7 @@ import {
|
|
|
1331
1346
|
keyFromHeaders,
|
|
1332
1347
|
resolveAgent
|
|
1333
1348
|
} from "@agentwares/agentguard-core";
|
|
1334
|
-
var PROXY_VERSION =
|
|
1349
|
+
var PROXY_VERSION = version();
|
|
1335
1350
|
function headerValue(extra, name) {
|
|
1336
1351
|
const raw = extra.requestInfo?.headers?.[name] ?? extra.requestInfo?.headers?.[name.toLowerCase()];
|
|
1337
1352
|
if (Array.isArray(raw)) return raw[0];
|
|
@@ -1559,7 +1574,7 @@ function createDownstreamServer(runtime, opts = {}) {
|
|
|
1559
1574
|
return server;
|
|
1560
1575
|
}
|
|
1561
1576
|
|
|
1562
|
-
// ../../packages/mcp-kit/dist/chunk-
|
|
1577
|
+
// ../../packages/mcp-kit/dist/chunk-NF4BQXVM.js
|
|
1563
1578
|
import { completable } from "@modelcontextprotocol/sdk/server/completable.js";
|
|
1564
1579
|
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1565
1580
|
import {
|
|
@@ -1571,6 +1586,7 @@ import {
|
|
|
1571
1586
|
import { z } from "zod";
|
|
1572
1587
|
|
|
1573
1588
|
// ../../packages/x402/dist/index.js
|
|
1589
|
+
var DEFAULT_X402_NETWORK = "eip155:8453";
|
|
1574
1590
|
var X402_HEADER = "X-Payment-Requirements";
|
|
1575
1591
|
var X402_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
1576
1592
|
function toAmount(priceUsd) {
|
|
@@ -1580,42 +1596,51 @@ function toAmount(priceUsd) {
|
|
|
1580
1596
|
}
|
|
1581
1597
|
function buildPaymentRequired(opts) {
|
|
1582
1598
|
const amountUsd = toAmount(opts.priceUsd);
|
|
1583
|
-
const
|
|
1584
|
-
|
|
1599
|
+
const creditsPayTo = opts.creditsPayTo ?? opts.selfServe?.keysUrl;
|
|
1600
|
+
const accepts = [];
|
|
1601
|
+
if (opts.payTo) {
|
|
1602
|
+
accepts.push({
|
|
1585
1603
|
rail: "x402",
|
|
1586
1604
|
amountUsd,
|
|
1587
1605
|
resource: opts.resource,
|
|
1588
1606
|
description: opts.description,
|
|
1589
|
-
network:
|
|
1607
|
+
network: opts.network ?? DEFAULT_X402_NETWORK,
|
|
1590
1608
|
asset: "USDC",
|
|
1591
1609
|
payTo: opts.payTo,
|
|
1592
1610
|
maxTimeoutSeconds: 300
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
if (creditsPayTo) {
|
|
1614
|
+
accepts.push({
|
|
1615
|
+
rail: "prepaid_credits",
|
|
1596
1616
|
amountUsd,
|
|
1597
1617
|
resource: opts.resource,
|
|
1598
1618
|
description: opts.description,
|
|
1599
1619
|
currency: "usd",
|
|
1600
|
-
payTo:
|
|
1601
|
-
maxTimeoutSeconds: 300
|
|
1602
|
-
|
|
1603
|
-
|
|
1620
|
+
payTo: creditsPayTo,
|
|
1621
|
+
maxTimeoutSeconds: 300,
|
|
1622
|
+
// funding a balance ends at a hosted Stripe Checkout page, which an agent cannot fill in
|
|
1623
|
+
humanRequired: true
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1604
1626
|
const live = opts.live === true;
|
|
1627
|
+
const selfServe = opts.selfServe;
|
|
1628
|
+
const liveFix = selfServe ? `Free first: retry with sample=true \u2014 no key, no payment. To pay: POST ${selfServe.keysUrl} (no auth, no signup) returns a key and a Stripe Checkout URL. Funding that key needs a person to complete the Checkout page in a browser; if you are running unattended, surface the URL to your operator rather than retrying. Once funded, send Authorization: Bearer <key>, plus Idempotency-Key so a repeated call is charged once.` : "Retry with a PAYMENT header satisfying one of `accepts`, or send an API key with credit balance.";
|
|
1605
1629
|
const body = {
|
|
1606
1630
|
code: "PAYMENT_REQUIRED",
|
|
1607
1631
|
cause: live ? `This call costs $${amountUsd}. No valid payment or credit was presented.` : `This call costs $${amountUsd}. Pay-per-call (x402 / Stripe MPP) is coming soon; use an API key with prepaid credits.`,
|
|
1608
|
-
fix: live ?
|
|
1632
|
+
fix: live ? liveFix : `Create an API key${opts.signupUrl ? ` at ${opts.signupUrl}` : ""} and retry with Authorization: Bearer <key>. Try the free sample mode first.`,
|
|
1609
1633
|
retryable: true,
|
|
1610
1634
|
priceUsd: opts.priceUsd,
|
|
1611
1635
|
accepts,
|
|
1612
1636
|
comingSoon: !live,
|
|
1613
|
-
alternatives: ["api_key_credits", "sample_mode"]
|
|
1637
|
+
alternatives: ["api_key_credits", "sample_mode"],
|
|
1638
|
+
...selfServe ? { selfServe } : {}
|
|
1614
1639
|
};
|
|
1615
1640
|
const encoded = JSON.stringify({ x402Version: 2, accepts, resource: opts.resource });
|
|
1616
1641
|
const headers = {
|
|
1617
1642
|
"Content-Type": "application/json",
|
|
1618
|
-
"WWW-Authenticate": `Payment realm="agentwares", methods="x402
|
|
1643
|
+
"WWW-Authenticate": `Payment realm="agentwares", methods="x402 prepaid_credits", price="${amountUsd} USD"`,
|
|
1619
1644
|
[X402_HEADER]: encoded,
|
|
1620
1645
|
[X402_REQUIRED_HEADER]: btoa(encoded),
|
|
1621
1646
|
"Cache-Control": "no-store"
|
|
@@ -1633,7 +1658,7 @@ var StubVerifier = class {
|
|
|
1633
1658
|
}
|
|
1634
1659
|
};
|
|
1635
1660
|
|
|
1636
|
-
// ../../packages/mcp-kit/dist/chunk-
|
|
1661
|
+
// ../../packages/mcp-kit/dist/chunk-NF4BQXVM.js
|
|
1637
1662
|
import { z as z2 } from "zod";
|
|
1638
1663
|
import { z as z3 } from "zod";
|
|
1639
1664
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -1815,13 +1840,13 @@ function defineTool(opts) {
|
|
|
1815
1840
|
annotations,
|
|
1816
1841
|
handler,
|
|
1817
1842
|
async invoke(rawInput, ctx) {
|
|
1818
|
-
const
|
|
1843
|
+
const fail2 = (err) => {
|
|
1819
1844
|
const result = errorResult(err, { requestId: ctx.requestId });
|
|
1820
1845
|
if (output) delete result.structuredContent;
|
|
1821
1846
|
return result;
|
|
1822
1847
|
};
|
|
1823
1848
|
const parsed = input.safeParse(rawInput ?? {});
|
|
1824
|
-
if (!parsed.success) return
|
|
1849
|
+
if (!parsed.success) return fail2(invalidInputError(parsed.error.issues));
|
|
1825
1850
|
try {
|
|
1826
1851
|
const value = await handler(parsed.data, ctx);
|
|
1827
1852
|
if (output && !isCallToolResult(value)) {
|
|
@@ -1839,7 +1864,7 @@ function defineTool(opts) {
|
|
|
1839
1864
|
}
|
|
1840
1865
|
return toCallToolResult2(value);
|
|
1841
1866
|
} catch (err) {
|
|
1842
|
-
return
|
|
1867
|
+
return fail2(err);
|
|
1843
1868
|
}
|
|
1844
1869
|
}
|
|
1845
1870
|
};
|
|
@@ -2080,6 +2105,7 @@ function withPayment(tool, opts) {
|
|
|
2080
2105
|
);
|
|
2081
2106
|
}
|
|
2082
2107
|
const verifier = opts.verifier ?? new StubVerifier();
|
|
2108
|
+
const liveRail = opts.verifier !== void 0;
|
|
2083
2109
|
const input = tool.input.extend({
|
|
2084
2110
|
[sampleField]: z2.boolean().default(false).describe(
|
|
2085
2111
|
"Set true to return an example response at no charge (sample mode). Default false."
|
|
@@ -2110,17 +2136,35 @@ function withPayment(tool, opts) {
|
|
|
2110
2136
|
live: opts.live,
|
|
2111
2137
|
description: opts.description,
|
|
2112
2138
|
payTo: opts.payTo,
|
|
2113
|
-
|
|
2139
|
+
creditsPayTo: opts.creditsPayTo,
|
|
2140
|
+
signupUrl: opts.signupUrl,
|
|
2141
|
+
selfServe: opts.selfServe
|
|
2114
2142
|
});
|
|
2115
|
-
const
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2143
|
+
const advertised = required.body.accepts.find((a) => a.rail === "prepaid_credits") ?? required.body.accepts[0];
|
|
2144
|
+
const requirement = advertised ?? {
|
|
2145
|
+
rail: "prepaid_credits",
|
|
2146
|
+
amountUsd: String(required.body.priceUsd),
|
|
2147
|
+
resource: tool.name
|
|
2148
|
+
};
|
|
2149
|
+
const verified = await verifier.verify(
|
|
2150
|
+
new Request(`https://mcp.local/${tool.name}`, { headers: ctx.headers }),
|
|
2151
|
+
requirement
|
|
2152
|
+
);
|
|
2153
|
+
if (verified.ok) {
|
|
2154
|
+
let result;
|
|
2155
|
+
try {
|
|
2156
|
+
result = await tool.handler(record, ctx);
|
|
2157
|
+
} catch (error) {
|
|
2158
|
+
await opts.onPaidFailure?.(ctx, verified, error);
|
|
2159
|
+
throw error;
|
|
2160
|
+
}
|
|
2161
|
+
const settled = await verifier.settle?.(verified);
|
|
2162
|
+
if (settled && !settled.ok) {
|
|
2163
|
+
opts.onSettlementFailure?.(ctx, verified, settled.reason);
|
|
2164
|
+
}
|
|
2165
|
+
return result;
|
|
2123
2166
|
}
|
|
2167
|
+
reason = liveRail ? verified.reason ?? reason : reason ?? verified.reason;
|
|
2124
2168
|
throw paymentRequiredError(required.body, reason);
|
|
2125
2169
|
}
|
|
2126
2170
|
});
|
|
@@ -2809,7 +2853,7 @@ import {
|
|
|
2809
2853
|
FileStateStore,
|
|
2810
2854
|
loadPolicyFile
|
|
2811
2855
|
} from "@agentwares/agentguard-core/node";
|
|
2812
|
-
import { existsSync as exists, readFileSync } from "fs";
|
|
2856
|
+
import { existsSync as exists, readFileSync as readFileSync2 } from "fs";
|
|
2813
2857
|
|
|
2814
2858
|
// ../../node_modules/.pnpm/postal-mime@2.7.5/node_modules/postal-mime/src/decode-strings.js
|
|
2815
2859
|
var textEncoder = new TextEncoder();
|
|
@@ -2842,8 +2886,8 @@ var MAX_HEADERS_SIZE = 2 * 1024 * 1024;
|
|
|
2842
2886
|
|
|
2843
2887
|
// ../../node_modules/.pnpm/resend@6.25.0/node_modules/resend/dist/index.mjs
|
|
2844
2888
|
var import_standardwebhooks = __toESM(require_dist(), 1);
|
|
2845
|
-
var
|
|
2846
|
-
var defaultUserAgent = `resend-node:${
|
|
2889
|
+
var version2 = "6.25.0";
|
|
2890
|
+
var defaultUserAgent = `resend-node:${version2}`;
|
|
2847
2891
|
|
|
2848
2892
|
// ../../packages/notify/dist/index.js
|
|
2849
2893
|
var palette = {
|
|
@@ -3193,7 +3237,7 @@ function readRegistration(loaded) {
|
|
|
3193
3237
|
const file = registrationPath(loaded.stateDir);
|
|
3194
3238
|
if (!exists(file)) return void 0;
|
|
3195
3239
|
try {
|
|
3196
|
-
const reg = JSON.parse(
|
|
3240
|
+
const reg = JSON.parse(readFileSync2(file, "utf8"));
|
|
3197
3241
|
return reg;
|
|
3198
3242
|
} catch {
|
|
3199
3243
|
return void 0;
|
|
@@ -3209,7 +3253,7 @@ function describeError(err) {
|
|
|
3209
3253
|
}
|
|
3210
3254
|
|
|
3211
3255
|
// src/configs.ts
|
|
3212
|
-
import { copyFileSync, existsSync as existsSync3, readFileSync as
|
|
3256
|
+
import { copyFileSync, existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
3213
3257
|
import { homedir as homedir2, platform } from "os";
|
|
3214
3258
|
import { join as join2, resolve as resolve2 } from "path";
|
|
3215
3259
|
function candidateConfigs(cwd = process.cwd(), home = homedir2(), env = process.env) {
|
|
@@ -3272,7 +3316,7 @@ function describeConfigPath(path2) {
|
|
|
3272
3316
|
if (known) return known;
|
|
3273
3317
|
let serversKey = "mcpServers";
|
|
3274
3318
|
try {
|
|
3275
|
-
const parsed = JSON.parse(
|
|
3319
|
+
const parsed = JSON.parse(readFileSync3(abs, "utf8"));
|
|
3276
3320
|
if (parsed.servers && !parsed.mcpServers) serversKey = "servers";
|
|
3277
3321
|
} catch {
|
|
3278
3322
|
}
|
|
@@ -3284,7 +3328,7 @@ function describeConfigPath(path2) {
|
|
|
3284
3328
|
};
|
|
3285
3329
|
}
|
|
3286
3330
|
function readServers(file) {
|
|
3287
|
-
const raw = JSON.parse(
|
|
3331
|
+
const raw = JSON.parse(readFileSync3(file.path, "utf8"));
|
|
3288
3332
|
const servers = raw[file.serversKey];
|
|
3289
3333
|
if (!servers || typeof servers !== "object") return {};
|
|
3290
3334
|
return servers;
|
|
@@ -3340,7 +3384,7 @@ function proxyEntry(file, policyPath, opts = {}) {
|
|
|
3340
3384
|
return entry;
|
|
3341
3385
|
}
|
|
3342
3386
|
function rewriteClientConfig(file, policyPath, opts = {}) {
|
|
3343
|
-
const raw = JSON.parse(
|
|
3387
|
+
const raw = JSON.parse(readFileSync3(file.path, "utf8"));
|
|
3344
3388
|
const servers = raw[file.serversKey] ?? {};
|
|
3345
3389
|
const replaced = Object.keys(servers).filter((n) => !isProxyEntry(servers[n]));
|
|
3346
3390
|
const backup = backupPath(file);
|
|
@@ -3355,6 +3399,16 @@ function restoreClientConfig(file) {
|
|
|
3355
3399
|
copyFileSync(backup, file.path);
|
|
3356
3400
|
return true;
|
|
3357
3401
|
}
|
|
3402
|
+
function mergeServerIntoConfig(file, name, entry) {
|
|
3403
|
+
const raw = JSON.parse(readFileSync3(file.path, "utf8"));
|
|
3404
|
+
const servers = raw[file.serversKey] ?? {};
|
|
3405
|
+
const replaced = name in servers;
|
|
3406
|
+
const backup = backupPath(file);
|
|
3407
|
+
if (!existsSync3(backup)) copyFileSync(file.path, backup);
|
|
3408
|
+
raw[file.serversKey] = { ...servers, [name]: entry };
|
|
3409
|
+
writeFileSync2(file.path, JSON.stringify(raw, null, 2) + "\n");
|
|
3410
|
+
return { backup, replaced };
|
|
3411
|
+
}
|
|
3358
3412
|
|
|
3359
3413
|
// src/commands/init.ts
|
|
3360
3414
|
import { existsSync as existsSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -4083,8 +4137,111 @@ ${steps.length} calls; halts: ${halts.join(", ") || "none"}. Now run: agentguard
|
|
|
4083
4137
|
}
|
|
4084
4138
|
}
|
|
4085
4139
|
|
|
4086
|
-
// src/
|
|
4087
|
-
import {
|
|
4140
|
+
// src/commands/connect.ts
|
|
4141
|
+
import { existsSync as existsSync5 } from "fs";
|
|
4142
|
+
import { relative as relative2 } from "path";
|
|
4143
|
+
var DEFAULT_PROXY_URL = "https://agentwares-agentguard-proxy.vercel.app";
|
|
4144
|
+
function fail(e) {
|
|
4145
|
+
throw e;
|
|
4146
|
+
}
|
|
4147
|
+
async function connectCommand(args, io, fetchImpl = fetch) {
|
|
4148
|
+
const key = args.positionals[0] ?? io.env.AGENTGUARD_KEY;
|
|
4149
|
+
if (!key) {
|
|
4150
|
+
io.err("usage: agentguard connect <key> [--write] [--client <path>] [--url <proxy-url>]");
|
|
4151
|
+
io.err(" the key is on your dashboard; it starts with agk_");
|
|
4152
|
+
return 2;
|
|
4153
|
+
}
|
|
4154
|
+
const base = (flagString(args, "url") ?? io.env.AGENTGUARD_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\/+$/, "");
|
|
4155
|
+
let res;
|
|
4156
|
+
try {
|
|
4157
|
+
res = await fetchImpl(`${base}/connect`, {
|
|
4158
|
+
method: "POST",
|
|
4159
|
+
headers: { "content-type": "application/json" },
|
|
4160
|
+
body: JSON.stringify({ key })
|
|
4161
|
+
});
|
|
4162
|
+
} catch (err) {
|
|
4163
|
+
fail({
|
|
4164
|
+
code: "PROXY_UNREACHABLE",
|
|
4165
|
+
cause: `could not reach ${base}: ${err instanceof Error ? err.message : String(err)}`,
|
|
4166
|
+
fix: "check the network, or pass --url if you run your own proxy",
|
|
4167
|
+
retryable: true
|
|
4168
|
+
});
|
|
4169
|
+
}
|
|
4170
|
+
const text = await res.text();
|
|
4171
|
+
let body;
|
|
4172
|
+
try {
|
|
4173
|
+
body = JSON.parse(text);
|
|
4174
|
+
} catch {
|
|
4175
|
+
fail({
|
|
4176
|
+
code: "PROXY_BAD_RESPONSE",
|
|
4177
|
+
cause: `${base}/connect returned ${res.status} that was not JSON: ${text.slice(0, 200)}`,
|
|
4178
|
+
fix: "check --url points at an agentguard proxy, not at the dashboard",
|
|
4179
|
+
retryable: false
|
|
4180
|
+
});
|
|
4181
|
+
}
|
|
4182
|
+
if (!res.ok) {
|
|
4183
|
+
const e = body;
|
|
4184
|
+
fail({
|
|
4185
|
+
code: e.code ?? "CONNECT_REFUSED",
|
|
4186
|
+
cause: e.cause ?? `${base}/connect returned ${res.status}`,
|
|
4187
|
+
fix: e.fix ?? "check the key on your dashboard; revoked keys stop working immediately",
|
|
4188
|
+
retryable: e.retryable ?? false
|
|
4189
|
+
});
|
|
4190
|
+
}
|
|
4191
|
+
const conn = body;
|
|
4192
|
+
if (flagBool(args, "json")) {
|
|
4193
|
+
io.out(JSON.stringify(conn, null, 2));
|
|
4194
|
+
return 0;
|
|
4195
|
+
}
|
|
4196
|
+
const rel = (p) => p.startsWith(io.cwd + "/") ? relative2(io.cwd, p) : p;
|
|
4197
|
+
const entry = conn.mcpServers[PROXY_SERVER_NAME] ?? {
|
|
4198
|
+
type: "http",
|
|
4199
|
+
url: conn.mcpUrl,
|
|
4200
|
+
headers: conn.headers
|
|
4201
|
+
};
|
|
4202
|
+
if (!flagBool(args, "write")) {
|
|
4203
|
+
io.out(JSON.stringify({ mcpServers: { [PROXY_SERVER_NAME]: entry } }, null, 2));
|
|
4204
|
+
} else {
|
|
4205
|
+
const explicit = flagString(args, "client");
|
|
4206
|
+
const configs = explicit ? [describeConfigPath(explicit)] : detectClientConfigs(io.cwd, homeFrom(io), io.env);
|
|
4207
|
+
const projectLevel = configs.filter((c) => c.path.startsWith(io.cwd + "/"));
|
|
4208
|
+
let chosen;
|
|
4209
|
+
if (explicit || flagBool(args, "all")) chosen = configs;
|
|
4210
|
+
else chosen = projectLevel.slice(0, 1);
|
|
4211
|
+
if (chosen.length === 0) {
|
|
4212
|
+
io.err(
|
|
4213
|
+
"no MCP config found here (.mcp.json, .cursor/mcp.json, .vscode/mcp.json). Create one, or paste this into your client:"
|
|
4214
|
+
);
|
|
4215
|
+
io.out(JSON.stringify({ mcpServers: { [PROXY_SERVER_NAME]: entry } }, null, 2));
|
|
4216
|
+
return 1;
|
|
4217
|
+
}
|
|
4218
|
+
for (const c of chosen) {
|
|
4219
|
+
if (!existsSync5(c.path)) {
|
|
4220
|
+
io.err(`no MCP config at ${c.path}`);
|
|
4221
|
+
return 1;
|
|
4222
|
+
}
|
|
4223
|
+
const { backup, replaced } = mergeServerIntoConfig(c, PROXY_SERVER_NAME, entry);
|
|
4224
|
+
io.out(
|
|
4225
|
+
`${replaced ? "updated" : "wrote "} ${rel(c.path)} (${c.label}) \u2192 ${PROXY_SERVER_NAME} = ${conn.mcpUrl}`
|
|
4226
|
+
);
|
|
4227
|
+
io.out(`backup ${rel(backup)}`);
|
|
4228
|
+
}
|
|
4229
|
+
for (const c of configs.filter((c2) => !chosen.includes(c2)))
|
|
4230
|
+
io.out(
|
|
4231
|
+
`found ${c.path} (${c.label}, user-level) \u2014 not touched; pass --client ${JSON.stringify(c.path)} to write it too`
|
|
4232
|
+
);
|
|
4233
|
+
}
|
|
4234
|
+
const band = conn.band ? ` band=${conn.band.tier} (${conn.band.quantity} calls/mo)` : "";
|
|
4235
|
+
io.out(`
|
|
4236
|
+
proxy ${conn.slug} (${conn.proxyId}) mode=${conn.mode}${band}`);
|
|
4237
|
+
if (conn.upstreams?.length) io.out(`upstreams: ${conn.upstreams.join(", ")}`);
|
|
4238
|
+
if (conn.dashboardUrl) io.out(`dashboard: ${conn.dashboardUrl}`);
|
|
4239
|
+
if (conn.claudeCode) io.out(`claude code: ${conn.claudeCode}`);
|
|
4240
|
+
if (!flagBool(args, "write"))
|
|
4241
|
+
io.out(`
|
|
4242
|
+
run again with --write to add this to your MCP client config`);
|
|
4243
|
+
return 0;
|
|
4244
|
+
}
|
|
4088
4245
|
|
|
4089
4246
|
// src/commands/control.ts
|
|
4090
4247
|
async function pokeHttp(io, reg, path2, body) {
|
|
@@ -4178,13 +4335,13 @@ async function approvalsCommand(args, io) {
|
|
|
4178
4335
|
}
|
|
4179
4336
|
|
|
4180
4337
|
// src/commands/keys.ts
|
|
4181
|
-
import { readFileSync as
|
|
4338
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
4182
4339
|
import YAML2, { isMap, isSeq, YAMLSeq } from "yaml";
|
|
4183
4340
|
import { generateAgentKey, hashAgentKey } from "@agentwares/agentguard-core";
|
|
4184
4341
|
async function keyCommand(args, io) {
|
|
4185
4342
|
const [sub, name] = args.positionals;
|
|
4186
4343
|
const path2 = policyPathFrom(args, io);
|
|
4187
|
-
const doc = YAML2.parseDocument(
|
|
4344
|
+
const doc = YAML2.parseDocument(readFileSync4(path2, "utf8"));
|
|
4188
4345
|
let agents = doc.get("agents");
|
|
4189
4346
|
if (!isSeq(agents)) {
|
|
4190
4347
|
agents = new YAMLSeq();
|
|
@@ -4461,19 +4618,13 @@ async function statusCommand(args, io) {
|
|
|
4461
4618
|
}
|
|
4462
4619
|
|
|
4463
4620
|
// src/cli.ts
|
|
4464
|
-
function version2() {
|
|
4465
|
-
try {
|
|
4466
|
-
return JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
4467
|
-
} catch {
|
|
4468
|
-
return "0.0.0";
|
|
4469
|
-
}
|
|
4470
|
-
}
|
|
4471
4621
|
var HELP = `agentguard \u2014 MCP policy proxy for agents that touch production
|
|
4472
4622
|
|
|
4473
4623
|
npx @agentwares/agentguard init read your MCP config, write agentguard.yaml (dry-run), route every server through the proxy
|
|
4474
4624
|
agentguard report [--run id|--all] what this run did / would have destroyed / spent, where it was halted
|
|
4475
4625
|
agentguard diff [--run id] record-by-record mutation diff from a dry run
|
|
4476
4626
|
agentguard verify [audit.jsonl] prove the hash-chained audit log was not edited
|
|
4627
|
+
agentguard connect <key> [--write] point this machine at your hosted proxy (paid tiers)
|
|
4477
4628
|
|
|
4478
4629
|
agentguard proxy [--http --port 8788] [--agent name] [--run-id id] [--mode dry-run|enforce]
|
|
4479
4630
|
agentguard tools [--json] the tools your agent will see, with class and why
|
|
@@ -4490,7 +4641,7 @@ Docs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli`;
|
|
|
4490
4641
|
async function main(argv, io = defaultIo()) {
|
|
4491
4642
|
const args = parseArgs(argv);
|
|
4492
4643
|
if (args.flags.version) {
|
|
4493
|
-
io.out(
|
|
4644
|
+
io.out(version());
|
|
4494
4645
|
return 0;
|
|
4495
4646
|
}
|
|
4496
4647
|
if (args.command === void 0 && !args.flags.help && io.tty === false) {
|
|
@@ -4518,6 +4669,8 @@ async function main(argv, io = defaultIo()) {
|
|
|
4518
4669
|
return await diffCommand(args, io);
|
|
4519
4670
|
case "verify":
|
|
4520
4671
|
return await verifyCommand(args, io);
|
|
4672
|
+
case "connect":
|
|
4673
|
+
return await connectCommand(args, io);
|
|
4521
4674
|
case "status":
|
|
4522
4675
|
return await statusCommand(args, io);
|
|
4523
4676
|
case "tools":
|