@agentwares/agentguard 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -17
- package/dist/cli.js +349 -155
- package/dist/cli.js.map +1 -1
- package/dist/fixtures/crm-server.js +92 -28
- package/dist/fixtures/crm-server.js.map +1 -1
- package/dist/fixtures/demo-agent.js +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +248 -54
- package/dist/index.js.map +1 -1
- package/package.json +16 -16
- package/server.json +2 -2
|
File without changes
|
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-7LRFDQNC.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,19 @@ import {
|
|
|
1571
1586
|
import { z } from "zod";
|
|
1572
1587
|
|
|
1573
1588
|
// ../../packages/x402/dist/index.js
|
|
1589
|
+
var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
1590
|
+
var USDC_DECIMALS = 6;
|
|
1591
|
+
var USDC_EIP712_DOMAIN = { name: "USD Coin", version: "2" };
|
|
1592
|
+
function toAtomicUnits(amountUsd, decimals = USDC_DECIMALS) {
|
|
1593
|
+
const trimmed = amountUsd.trim();
|
|
1594
|
+
if (!/^\d+(\.\d+)?$/.test(trimmed)) throw new Error(`not a decimal amount: ${amountUsd}`);
|
|
1595
|
+
const [whole = "0", fraction = ""] = trimmed.split(".");
|
|
1596
|
+
if (fraction.length > decimals) {
|
|
1597
|
+
throw new Error(`${amountUsd} has more than ${decimals} decimal places`);
|
|
1598
|
+
}
|
|
1599
|
+
return `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
|
|
1600
|
+
}
|
|
1601
|
+
var DEFAULT_X402_NETWORK = "eip155:8453";
|
|
1574
1602
|
var X402_HEADER = "X-Payment-Requirements";
|
|
1575
1603
|
var X402_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
1576
1604
|
function toAmount(priceUsd) {
|
|
@@ -1580,42 +1608,69 @@ function toAmount(priceUsd) {
|
|
|
1580
1608
|
}
|
|
1581
1609
|
function buildPaymentRequired(opts) {
|
|
1582
1610
|
const amountUsd = toAmount(opts.priceUsd);
|
|
1583
|
-
const
|
|
1584
|
-
|
|
1611
|
+
const creditsPayTo = opts.creditsPayTo ?? opts.selfServe?.keysUrl;
|
|
1612
|
+
const accepts = [];
|
|
1613
|
+
if (opts.payTo) {
|
|
1614
|
+
accepts.push({
|
|
1585
1615
|
rail: "x402",
|
|
1616
|
+
scheme: "exact",
|
|
1586
1617
|
amountUsd,
|
|
1618
|
+
amount: toAtomicUnits(amountUsd, opts.assetDecimals),
|
|
1587
1619
|
resource: opts.resource,
|
|
1588
1620
|
description: opts.description,
|
|
1589
|
-
|
|
1590
|
-
|
|
1621
|
+
mimeType: "application/json",
|
|
1622
|
+
network: opts.network ?? DEFAULT_X402_NETWORK,
|
|
1623
|
+
asset: opts.asset ?? USDC_BASE,
|
|
1624
|
+
extra: opts.assetDomain ?? USDC_EIP712_DOMAIN,
|
|
1591
1625
|
payTo: opts.payTo,
|
|
1592
|
-
maxTimeoutSeconds: 300
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1626
|
+
maxTimeoutSeconds: 300,
|
|
1627
|
+
...opts.bazaar ? { bazaar: opts.bazaar } : {}
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
if (creditsPayTo) {
|
|
1631
|
+
accepts.push({
|
|
1632
|
+
rail: "prepaid_credits",
|
|
1596
1633
|
amountUsd,
|
|
1597
1634
|
resource: opts.resource,
|
|
1598
1635
|
description: opts.description,
|
|
1599
1636
|
currency: "usd",
|
|
1600
|
-
payTo:
|
|
1601
|
-
maxTimeoutSeconds: 300
|
|
1602
|
-
|
|
1603
|
-
|
|
1637
|
+
payTo: creditsPayTo,
|
|
1638
|
+
maxTimeoutSeconds: 300,
|
|
1639
|
+
// funding a balance ends at a hosted Stripe Checkout page, which an agent cannot fill in
|
|
1640
|
+
humanRequired: true
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1604
1643
|
const live = opts.live === true;
|
|
1644
|
+
const selfServe = opts.selfServe;
|
|
1645
|
+
const x402Rail = accepts.find((a) => a.rail === "x402" && a.payTo);
|
|
1646
|
+
const usdcFix = x402Rail ? `Pay in USDC with no account and no human: take the \`accepts\` entry with \`rail: "x402"\`, sign an EIP-3009 transferWithAuthorization for ${x402Rail.amountUsd} USDC to ${x402Rail.payTo} on ${x402Rail.network}, and retry with it base64 in the X-PAYMENT header. The facilitator submits the transfer, so you need USDC and no gas. ` : "";
|
|
1647
|
+
const sampleFix = opts.sampleMode ? "Free first: retry with sample=true \u2014 no key, no payment. " : "";
|
|
1648
|
+
const cardFix = selfServe ? `Or pay by card: POST ${selfServe.keysUrl} (no auth, no signup) returns a key and a Stripe Checkout URL. Funding that key needs a person at a browser; if you are running unattended, surface the URL to your operator rather than retrying, or use the USDC rail above. Once funded, send Authorization: Bearer <key>, plus Idempotency-Key so a repeated call is charged once.` : "";
|
|
1649
|
+
const liveFix = usdcFix || cardFix ? `${sampleFix}${usdcFix}${cardFix}`.trim() : "Retry with a PAYMENT header satisfying one of `accepts`, or send an API key with credit balance.";
|
|
1605
1650
|
const body = {
|
|
1606
1651
|
code: "PAYMENT_REQUIRED",
|
|
1607
1652
|
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 ?
|
|
1653
|
+
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
1654
|
retryable: true,
|
|
1610
1655
|
priceUsd: opts.priceUsd,
|
|
1611
1656
|
accepts,
|
|
1612
1657
|
comingSoon: !live,
|
|
1613
|
-
alternatives: [
|
|
1658
|
+
alternatives: [
|
|
1659
|
+
...creditsPayTo ? ["api_key_credits"] : [],
|
|
1660
|
+
...opts.sampleMode ? ["sample_mode"] : []
|
|
1661
|
+
],
|
|
1662
|
+
...selfServe ? { selfServe } : {},
|
|
1663
|
+
...opts.bazaar ? { extensions: { bazaar: opts.bazaar } } : {}
|
|
1614
1664
|
};
|
|
1615
|
-
const encoded = JSON.stringify({
|
|
1665
|
+
const encoded = JSON.stringify({
|
|
1666
|
+
x402Version: 2,
|
|
1667
|
+
accepts,
|
|
1668
|
+
resource: opts.resource,
|
|
1669
|
+
...opts.bazaar ? { extensions: { bazaar: opts.bazaar } } : {}
|
|
1670
|
+
});
|
|
1616
1671
|
const headers = {
|
|
1617
1672
|
"Content-Type": "application/json",
|
|
1618
|
-
"WWW-Authenticate": `Payment realm="agentwares", methods="x402
|
|
1673
|
+
"WWW-Authenticate": `Payment realm="agentwares", methods="x402 prepaid_credits", price="${amountUsd} USD"`,
|
|
1619
1674
|
[X402_HEADER]: encoded,
|
|
1620
1675
|
[X402_REQUIRED_HEADER]: btoa(encoded),
|
|
1621
1676
|
"Cache-Control": "no-store"
|
|
@@ -1633,7 +1688,7 @@ var StubVerifier = class {
|
|
|
1633
1688
|
}
|
|
1634
1689
|
};
|
|
1635
1690
|
|
|
1636
|
-
// ../../packages/mcp-kit/dist/chunk-
|
|
1691
|
+
// ../../packages/mcp-kit/dist/chunk-7LRFDQNC.js
|
|
1637
1692
|
import { z as z2 } from "zod";
|
|
1638
1693
|
import { z as z3 } from "zod";
|
|
1639
1694
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -1815,13 +1870,13 @@ function defineTool(opts) {
|
|
|
1815
1870
|
annotations,
|
|
1816
1871
|
handler,
|
|
1817
1872
|
async invoke(rawInput, ctx) {
|
|
1818
|
-
const
|
|
1873
|
+
const fail2 = (err) => {
|
|
1819
1874
|
const result = errorResult(err, { requestId: ctx.requestId });
|
|
1820
1875
|
if (output) delete result.structuredContent;
|
|
1821
1876
|
return result;
|
|
1822
1877
|
};
|
|
1823
1878
|
const parsed = input.safeParse(rawInput ?? {});
|
|
1824
|
-
if (!parsed.success) return
|
|
1879
|
+
if (!parsed.success) return fail2(invalidInputError(parsed.error.issues));
|
|
1825
1880
|
try {
|
|
1826
1881
|
const value = await handler(parsed.data, ctx);
|
|
1827
1882
|
if (output && !isCallToolResult(value)) {
|
|
@@ -1839,7 +1894,7 @@ function defineTool(opts) {
|
|
|
1839
1894
|
}
|
|
1840
1895
|
return toCallToolResult2(value);
|
|
1841
1896
|
} catch (err) {
|
|
1842
|
-
return
|
|
1897
|
+
return fail2(err);
|
|
1843
1898
|
}
|
|
1844
1899
|
}
|
|
1845
1900
|
};
|
|
@@ -2080,6 +2135,7 @@ function withPayment(tool, opts) {
|
|
|
2080
2135
|
);
|
|
2081
2136
|
}
|
|
2082
2137
|
const verifier = opts.verifier ?? new StubVerifier();
|
|
2138
|
+
const liveRail = opts.verifier !== void 0;
|
|
2083
2139
|
const input = tool.input.extend({
|
|
2084
2140
|
[sampleField]: z2.boolean().default(false).describe(
|
|
2085
2141
|
"Set true to return an example response at no charge (sample mode). Default false."
|
|
@@ -2104,23 +2160,46 @@ function withPayment(tool, opts) {
|
|
|
2104
2160
|
if (auth.ok) return tool.handler(record, { ...ctx, userId: auth.userId });
|
|
2105
2161
|
reason = auth.reason;
|
|
2106
2162
|
}
|
|
2163
|
+
const resource = ctx.resourceUrl ?? tool.name;
|
|
2107
2164
|
const required = buildPaymentRequired({
|
|
2108
|
-
resource
|
|
2165
|
+
resource,
|
|
2109
2166
|
priceUsd: opts.priceUsd,
|
|
2110
2167
|
live: opts.live,
|
|
2111
2168
|
description: opts.description,
|
|
2112
2169
|
payTo: opts.payTo,
|
|
2113
|
-
|
|
2170
|
+
creditsPayTo: opts.creditsPayTo,
|
|
2171
|
+
signupUrl: opts.signupUrl,
|
|
2172
|
+
selfServe: opts.selfServe,
|
|
2173
|
+
// withPayment always adds a free `sample` flag to the tool's input schema
|
|
2174
|
+
sampleMode: true,
|
|
2175
|
+
...opts.bazaar ? { bazaar: opts.bazaar } : {}
|
|
2114
2176
|
});
|
|
2115
|
-
const
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2177
|
+
const advertised = required.body.accepts.find((a) => a.rail === "prepaid_credits") ?? required.body.accepts[0];
|
|
2178
|
+
const requirement = advertised ?? {
|
|
2179
|
+
rail: "prepaid_credits",
|
|
2180
|
+
amountUsd: String(required.body.priceUsd),
|
|
2181
|
+
resource,
|
|
2182
|
+
...opts.description ? { description: opts.description } : {}
|
|
2183
|
+
};
|
|
2184
|
+
const verified = await verifier.verify(
|
|
2185
|
+
new Request(`https://mcp.local/${tool.name}`, { headers: ctx.headers }),
|
|
2186
|
+
requirement
|
|
2187
|
+
);
|
|
2188
|
+
if (verified.ok) {
|
|
2189
|
+
let result;
|
|
2190
|
+
try {
|
|
2191
|
+
result = await tool.handler(record, ctx);
|
|
2192
|
+
} catch (error) {
|
|
2193
|
+
await opts.onPaidFailure?.(ctx, verified, error);
|
|
2194
|
+
throw error;
|
|
2195
|
+
}
|
|
2196
|
+
const settled = await verifier.settle?.(verified);
|
|
2197
|
+
if (settled && !settled.ok) {
|
|
2198
|
+
opts.onSettlementFailure?.(ctx, verified, settled.reason);
|
|
2199
|
+
}
|
|
2200
|
+
return result;
|
|
2123
2201
|
}
|
|
2202
|
+
reason = liveRail ? verified.reason ?? reason : reason ?? verified.reason;
|
|
2124
2203
|
throw paymentRequiredError(required.body, reason);
|
|
2125
2204
|
}
|
|
2126
2205
|
});
|
|
@@ -2809,7 +2888,7 @@ import {
|
|
|
2809
2888
|
FileStateStore,
|
|
2810
2889
|
loadPolicyFile
|
|
2811
2890
|
} from "@agentwares/agentguard-core/node";
|
|
2812
|
-
import { existsSync as exists, readFileSync } from "fs";
|
|
2891
|
+
import { existsSync as exists, readFileSync as readFileSync2 } from "fs";
|
|
2813
2892
|
|
|
2814
2893
|
// ../../node_modules/.pnpm/postal-mime@2.7.5/node_modules/postal-mime/src/decode-strings.js
|
|
2815
2894
|
var textEncoder = new TextEncoder();
|
|
@@ -2842,8 +2921,8 @@ var MAX_HEADERS_SIZE = 2 * 1024 * 1024;
|
|
|
2842
2921
|
|
|
2843
2922
|
// ../../node_modules/.pnpm/resend@6.25.0/node_modules/resend/dist/index.mjs
|
|
2844
2923
|
var import_standardwebhooks = __toESM(require_dist(), 1);
|
|
2845
|
-
var
|
|
2846
|
-
var defaultUserAgent = `resend-node:${
|
|
2924
|
+
var version2 = "6.25.0";
|
|
2925
|
+
var defaultUserAgent = `resend-node:${version2}`;
|
|
2847
2926
|
|
|
2848
2927
|
// ../../packages/notify/dist/index.js
|
|
2849
2928
|
var palette = {
|
|
@@ -3193,7 +3272,7 @@ function readRegistration(loaded) {
|
|
|
3193
3272
|
const file = registrationPath(loaded.stateDir);
|
|
3194
3273
|
if (!exists(file)) return void 0;
|
|
3195
3274
|
try {
|
|
3196
|
-
const reg = JSON.parse(
|
|
3275
|
+
const reg = JSON.parse(readFileSync2(file, "utf8"));
|
|
3197
3276
|
return reg;
|
|
3198
3277
|
} catch {
|
|
3199
3278
|
return void 0;
|
|
@@ -3209,7 +3288,7 @@ function describeError(err) {
|
|
|
3209
3288
|
}
|
|
3210
3289
|
|
|
3211
3290
|
// src/configs.ts
|
|
3212
|
-
import { copyFileSync, existsSync as existsSync3, readFileSync as
|
|
3291
|
+
import { copyFileSync, existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
3213
3292
|
import { homedir as homedir2, platform } from "os";
|
|
3214
3293
|
import { join as join2, resolve as resolve2 } from "path";
|
|
3215
3294
|
function candidateConfigs(cwd = process.cwd(), home = homedir2(), env = process.env) {
|
|
@@ -3272,7 +3351,7 @@ function describeConfigPath(path2) {
|
|
|
3272
3351
|
if (known) return known;
|
|
3273
3352
|
let serversKey = "mcpServers";
|
|
3274
3353
|
try {
|
|
3275
|
-
const parsed = JSON.parse(
|
|
3354
|
+
const parsed = JSON.parse(readFileSync3(abs, "utf8"));
|
|
3276
3355
|
if (parsed.servers && !parsed.mcpServers) serversKey = "servers";
|
|
3277
3356
|
} catch {
|
|
3278
3357
|
}
|
|
@@ -3284,12 +3363,13 @@ function describeConfigPath(path2) {
|
|
|
3284
3363
|
};
|
|
3285
3364
|
}
|
|
3286
3365
|
function readServers(file) {
|
|
3287
|
-
const raw = JSON.parse(
|
|
3366
|
+
const raw = JSON.parse(readFileSync3(file.path, "utf8"));
|
|
3288
3367
|
const servers = raw[file.serversKey];
|
|
3289
3368
|
if (!servers || typeof servers !== "object") return {};
|
|
3290
3369
|
return servers;
|
|
3291
3370
|
}
|
|
3292
3371
|
var PROXY_SERVER_NAME = "agentguard";
|
|
3372
|
+
var PROXY_PACKAGE = "@agentwares/agentguard";
|
|
3293
3373
|
function isProxyEntry(entry) {
|
|
3294
3374
|
return (entry.args ?? []).some((a) => a === "agentguard" || /(^|\/)agentguard(\.js)?$/.test(a)) && (entry.args ?? []).includes("proxy");
|
|
3295
3375
|
}
|
|
@@ -3333,14 +3413,14 @@ function backupPath(file) {
|
|
|
3333
3413
|
return `${file.path}.agentguard-backup`;
|
|
3334
3414
|
}
|
|
3335
3415
|
function proxyEntry(file, policyPath, opts = {}) {
|
|
3336
|
-
const args = ["-y",
|
|
3416
|
+
const args = ["-y", PROXY_PACKAGE, "proxy", "--config", policyPath];
|
|
3337
3417
|
if (opts.agent) args.push("--agent", opts.agent);
|
|
3338
3418
|
const entry = { command: "npx", args };
|
|
3339
3419
|
if (file.serversKey === "servers") entry.type = "stdio";
|
|
3340
3420
|
return entry;
|
|
3341
3421
|
}
|
|
3342
3422
|
function rewriteClientConfig(file, policyPath, opts = {}) {
|
|
3343
|
-
const raw = JSON.parse(
|
|
3423
|
+
const raw = JSON.parse(readFileSync3(file.path, "utf8"));
|
|
3344
3424
|
const servers = raw[file.serversKey] ?? {};
|
|
3345
3425
|
const replaced = Object.keys(servers).filter((n) => !isProxyEntry(servers[n]));
|
|
3346
3426
|
const backup = backupPath(file);
|
|
@@ -3355,6 +3435,16 @@ function restoreClientConfig(file) {
|
|
|
3355
3435
|
copyFileSync(backup, file.path);
|
|
3356
3436
|
return true;
|
|
3357
3437
|
}
|
|
3438
|
+
function mergeServerIntoConfig(file, name, entry) {
|
|
3439
|
+
const raw = JSON.parse(readFileSync3(file.path, "utf8"));
|
|
3440
|
+
const servers = raw[file.serversKey] ?? {};
|
|
3441
|
+
const replaced = name in servers;
|
|
3442
|
+
const backup = backupPath(file);
|
|
3443
|
+
if (!existsSync3(backup)) copyFileSync(file.path, backup);
|
|
3444
|
+
raw[file.serversKey] = { ...servers, [name]: entry };
|
|
3445
|
+
writeFileSync2(file.path, JSON.stringify(raw, null, 2) + "\n");
|
|
3446
|
+
return { backup, replaced };
|
|
3447
|
+
}
|
|
3358
3448
|
|
|
3359
3449
|
// src/commands/init.ts
|
|
3360
3450
|
import { existsSync as existsSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -3671,6 +3761,11 @@ async function initCommand(args, io) {
|
|
|
3671
3761
|
}
|
|
3672
3762
|
if (flagBool(args, "rewrite") !== false) {
|
|
3673
3763
|
for (const c of chosen) {
|
|
3764
|
+
if (resolve3(c.path) === resolve3(policyPath)) {
|
|
3765
|
+
throw new Error(
|
|
3766
|
+
`refusing: ${rel(c.path)} is both the policy file (--config) and a client config. Rewriting it would replace the policy with the proxy entry and leave the proxy pointing at itself. Use --config for the policy path and --client for the client's config, or let --config default to agentguard.yaml.`
|
|
3767
|
+
);
|
|
3768
|
+
}
|
|
3674
3769
|
const { backup, replaced } = rewriteClientConfig(c, policyPath, {
|
|
3675
3770
|
agent: flagString(args, "agent")
|
|
3676
3771
|
});
|
|
@@ -4083,8 +4178,111 @@ ${steps.length} calls; halts: ${halts.join(", ") || "none"}. Now run: agentguard
|
|
|
4083
4178
|
}
|
|
4084
4179
|
}
|
|
4085
4180
|
|
|
4086
|
-
// src/
|
|
4087
|
-
import {
|
|
4181
|
+
// src/commands/connect.ts
|
|
4182
|
+
import { existsSync as existsSync5 } from "fs";
|
|
4183
|
+
import { relative as relative2 } from "path";
|
|
4184
|
+
var DEFAULT_PROXY_URL = "https://agentwares-agentguard-proxy.vercel.app";
|
|
4185
|
+
function fail(e) {
|
|
4186
|
+
throw e;
|
|
4187
|
+
}
|
|
4188
|
+
async function connectCommand(args, io, fetchImpl = fetch) {
|
|
4189
|
+
const key = args.positionals[0] ?? io.env.AGENTGUARD_KEY;
|
|
4190
|
+
if (!key) {
|
|
4191
|
+
io.err("usage: agentguard connect <key> [--write] [--client <path>] [--url <proxy-url>]");
|
|
4192
|
+
io.err(" the key is on your dashboard; it starts with agk_");
|
|
4193
|
+
return 2;
|
|
4194
|
+
}
|
|
4195
|
+
const base = (flagString(args, "url") ?? io.env.AGENTGUARD_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\/+$/, "");
|
|
4196
|
+
let res;
|
|
4197
|
+
try {
|
|
4198
|
+
res = await fetchImpl(`${base}/connect`, {
|
|
4199
|
+
method: "POST",
|
|
4200
|
+
headers: { "content-type": "application/json" },
|
|
4201
|
+
body: JSON.stringify({ key })
|
|
4202
|
+
});
|
|
4203
|
+
} catch (err) {
|
|
4204
|
+
fail({
|
|
4205
|
+
code: "PROXY_UNREACHABLE",
|
|
4206
|
+
cause: `could not reach ${base}: ${err instanceof Error ? err.message : String(err)}`,
|
|
4207
|
+
fix: "check the network, or pass --url if you run your own proxy",
|
|
4208
|
+
retryable: true
|
|
4209
|
+
});
|
|
4210
|
+
}
|
|
4211
|
+
const text = await res.text();
|
|
4212
|
+
let body;
|
|
4213
|
+
try {
|
|
4214
|
+
body = JSON.parse(text);
|
|
4215
|
+
} catch {
|
|
4216
|
+
fail({
|
|
4217
|
+
code: "PROXY_BAD_RESPONSE",
|
|
4218
|
+
cause: `${base}/connect returned ${res.status} that was not JSON: ${text.slice(0, 200)}`,
|
|
4219
|
+
fix: "check --url points at an agentguard proxy, not at the dashboard",
|
|
4220
|
+
retryable: false
|
|
4221
|
+
});
|
|
4222
|
+
}
|
|
4223
|
+
if (!res.ok) {
|
|
4224
|
+
const e = body;
|
|
4225
|
+
fail({
|
|
4226
|
+
code: e.code ?? "CONNECT_REFUSED",
|
|
4227
|
+
cause: e.cause ?? `${base}/connect returned ${res.status}`,
|
|
4228
|
+
fix: e.fix ?? "check the key on your dashboard; revoked keys stop working immediately",
|
|
4229
|
+
retryable: e.retryable ?? false
|
|
4230
|
+
});
|
|
4231
|
+
}
|
|
4232
|
+
const conn = body;
|
|
4233
|
+
if (flagBool(args, "json")) {
|
|
4234
|
+
io.out(JSON.stringify(conn, null, 2));
|
|
4235
|
+
return 0;
|
|
4236
|
+
}
|
|
4237
|
+
const rel = (p) => p.startsWith(io.cwd + "/") ? relative2(io.cwd, p) : p;
|
|
4238
|
+
const entry = conn.mcpServers[PROXY_SERVER_NAME] ?? {
|
|
4239
|
+
type: "http",
|
|
4240
|
+
url: conn.mcpUrl,
|
|
4241
|
+
headers: conn.headers
|
|
4242
|
+
};
|
|
4243
|
+
if (!flagBool(args, "write")) {
|
|
4244
|
+
io.out(JSON.stringify({ mcpServers: { [PROXY_SERVER_NAME]: entry } }, null, 2));
|
|
4245
|
+
} else {
|
|
4246
|
+
const explicit = flagString(args, "client");
|
|
4247
|
+
const configs = explicit ? [describeConfigPath(explicit)] : detectClientConfigs(io.cwd, homeFrom(io), io.env);
|
|
4248
|
+
const projectLevel = configs.filter((c) => c.path.startsWith(io.cwd + "/"));
|
|
4249
|
+
let chosen;
|
|
4250
|
+
if (explicit || flagBool(args, "all")) chosen = configs;
|
|
4251
|
+
else chosen = projectLevel.slice(0, 1);
|
|
4252
|
+
if (chosen.length === 0) {
|
|
4253
|
+
io.err(
|
|
4254
|
+
"no MCP config found here (.mcp.json, .cursor/mcp.json, .vscode/mcp.json). Create one, or paste this into your client:"
|
|
4255
|
+
);
|
|
4256
|
+
io.out(JSON.stringify({ mcpServers: { [PROXY_SERVER_NAME]: entry } }, null, 2));
|
|
4257
|
+
return 1;
|
|
4258
|
+
}
|
|
4259
|
+
for (const c of chosen) {
|
|
4260
|
+
if (!existsSync5(c.path)) {
|
|
4261
|
+
io.err(`no MCP config at ${c.path}`);
|
|
4262
|
+
return 1;
|
|
4263
|
+
}
|
|
4264
|
+
const { backup, replaced } = mergeServerIntoConfig(c, PROXY_SERVER_NAME, entry);
|
|
4265
|
+
io.out(
|
|
4266
|
+
`${replaced ? "updated" : "wrote "} ${rel(c.path)} (${c.label}) \u2192 ${PROXY_SERVER_NAME} = ${conn.mcpUrl}`
|
|
4267
|
+
);
|
|
4268
|
+
io.out(`backup ${rel(backup)}`);
|
|
4269
|
+
}
|
|
4270
|
+
for (const c of configs.filter((c2) => !chosen.includes(c2)))
|
|
4271
|
+
io.out(
|
|
4272
|
+
`found ${c.path} (${c.label}, user-level) \u2014 not touched; pass --client ${JSON.stringify(c.path)} to write it too`
|
|
4273
|
+
);
|
|
4274
|
+
}
|
|
4275
|
+
const band = conn.band ? ` band=${conn.band.tier} (${conn.band.quantity} calls/mo)` : "";
|
|
4276
|
+
io.out(`
|
|
4277
|
+
proxy ${conn.slug} (${conn.proxyId}) mode=${conn.mode}${band}`);
|
|
4278
|
+
if (conn.upstreams?.length) io.out(`upstreams: ${conn.upstreams.join(", ")}`);
|
|
4279
|
+
if (conn.dashboardUrl) io.out(`dashboard: ${conn.dashboardUrl}`);
|
|
4280
|
+
if (conn.claudeCode) io.out(`claude code: ${conn.claudeCode}`);
|
|
4281
|
+
if (!flagBool(args, "write"))
|
|
4282
|
+
io.out(`
|
|
4283
|
+
run again with --write to add this to your MCP client config`);
|
|
4284
|
+
return 0;
|
|
4285
|
+
}
|
|
4088
4286
|
|
|
4089
4287
|
// src/commands/control.ts
|
|
4090
4288
|
async function pokeHttp(io, reg, path2, body) {
|
|
@@ -4178,13 +4376,13 @@ async function approvalsCommand(args, io) {
|
|
|
4178
4376
|
}
|
|
4179
4377
|
|
|
4180
4378
|
// src/commands/keys.ts
|
|
4181
|
-
import { readFileSync as
|
|
4379
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
4182
4380
|
import YAML2, { isMap, isSeq, YAMLSeq } from "yaml";
|
|
4183
4381
|
import { generateAgentKey, hashAgentKey } from "@agentwares/agentguard-core";
|
|
4184
4382
|
async function keyCommand(args, io) {
|
|
4185
4383
|
const [sub, name] = args.positionals;
|
|
4186
4384
|
const path2 = policyPathFrom(args, io);
|
|
4187
|
-
const doc = YAML2.parseDocument(
|
|
4385
|
+
const doc = YAML2.parseDocument(readFileSync4(path2, "utf8"));
|
|
4188
4386
|
let agents = doc.get("agents");
|
|
4189
4387
|
if (!isSeq(agents)) {
|
|
4190
4388
|
agents = new YAMLSeq();
|
|
@@ -4461,19 +4659,13 @@ async function statusCommand(args, io) {
|
|
|
4461
4659
|
}
|
|
4462
4660
|
|
|
4463
4661
|
// 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
4662
|
var HELP = `agentguard \u2014 MCP policy proxy for agents that touch production
|
|
4472
4663
|
|
|
4473
4664
|
npx @agentwares/agentguard init read your MCP config, write agentguard.yaml (dry-run), route every server through the proxy
|
|
4474
4665
|
agentguard report [--run id|--all] what this run did / would have destroyed / spent, where it was halted
|
|
4475
4666
|
agentguard diff [--run id] record-by-record mutation diff from a dry run
|
|
4476
4667
|
agentguard verify [audit.jsonl] prove the hash-chained audit log was not edited
|
|
4668
|
+
agentguard connect <key> [--write] point this machine at your hosted proxy (paid tiers)
|
|
4477
4669
|
|
|
4478
4670
|
agentguard proxy [--http --port 8788] [--agent name] [--run-id id] [--mode dry-run|enforce]
|
|
4479
4671
|
agentguard tools [--json] the tools your agent will see, with class and why
|
|
@@ -4490,7 +4682,7 @@ Docs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli`;
|
|
|
4490
4682
|
async function main(argv, io = defaultIo()) {
|
|
4491
4683
|
const args = parseArgs(argv);
|
|
4492
4684
|
if (args.flags.version) {
|
|
4493
|
-
io.out(
|
|
4685
|
+
io.out(version());
|
|
4494
4686
|
return 0;
|
|
4495
4687
|
}
|
|
4496
4688
|
if (args.command === void 0 && !args.flags.help && io.tty === false) {
|
|
@@ -4518,6 +4710,8 @@ async function main(argv, io = defaultIo()) {
|
|
|
4518
4710
|
return await diffCommand(args, io);
|
|
4519
4711
|
case "verify":
|
|
4520
4712
|
return await verifyCommand(args, io);
|
|
4713
|
+
case "connect":
|
|
4714
|
+
return await connectCommand(args, io);
|
|
4521
4715
|
case "status":
|
|
4522
4716
|
return await statusCommand(args, io);
|
|
4523
4717
|
case "tools":
|