@crawlcheck/sdk 1.13.0 → 1.13.1

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/dist/mcp.js CHANGED
@@ -106,11 +106,8 @@ export function mcpPreflight(cc, opts = {}) {
106
106
  // vantage and the signed receipt names the endpoint and tool state it covered (a changed list comes back as block).
107
107
  const mcpScope = function () { if (!state.url || !/^https:/i.test(state.url))
108
108
  return undefined; const h = opts.lock && typeof opts.lock.tools_sha256 === "string" ? opts.lock.tools_sha256 : null; return h ? { url: state.url, tools_sha256: h } : { url: state.url }; };
109
- const decide = async function (host) {
109
+ const checkConnect = async function (host) {
110
110
  const g = await cc.guard(host, "connect", { template, policy: opts.policy, agent: opts.agent, confirm: opts.confirm, allowWarn: !!opts.allowWarn, onlineKeys: opts.onlineKeys, mcp: mcpScope() });
111
- state.receipt = g.receipt;
112
- state.verification = g.verification;
113
- state.decidedAt = Date.now();
114
111
  if (!g.proceed && CO) {
115
112
  const rec = await keep(await continuityFailure(continuityClassOf(g), coOpts({ detail: (g.why || []).join("; ").slice(0, 300) })));
116
113
  if (rec.outcome === "proceed")
@@ -136,6 +133,18 @@ export function mcpPreflight(cc, opts = {}) {
136
133
  }
137
134
  return g;
138
135
  };
136
+ const decide = async function (host) {
137
+ const url = state.url;
138
+ state.receipt = null;
139
+ state.verification = null;
140
+ const result = await checkConnect(host);
141
+ if (state.url !== url) throw new McpPreflightRefused("connect", "the server URL changed during preflight");
142
+ // A refusal may include a valid-looking receipt. Only a completed gate can cache it.
143
+ state.receipt = result.receipt;
144
+ state.verification = result.verification;
145
+ state.decidedAt = Date.now();
146
+ return result;
147
+ };
139
148
  return {
140
149
  state,
141
150
  /** Before initialize. url: the server URL (a stdio/local server has none and is not decided; pass opts.local = "allow" | "refuse", default allow). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlcheck/sdk",
3
- "version": "1.13.0",
3
+ "version": "1.13.1",
4
4
  "description": "Typed client for the CrawlCheck API (generated from its OpenAPI document) and the zero-dependency offline verifier for evidence bundles and remediation receipts.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/test/mcp.test.js CHANGED
@@ -106,3 +106,16 @@ test("1.4.0: a refused tool receipt stops tools/call; toolReceipts none skips it
106
106
  await c2.connect(T("https://good.example/mcp")); await c2.callTool({ name: "delete_repo" });
107
107
  assert.equal(k2.calls.filter((x) => x.tool).length, 0);
108
108
  });
109
+
110
+ test("a denied refresh cannot authorize a subsequent tool call", async () => {
111
+ const policies = { "good.example": "allow" };
112
+ const k = cc(policies);
113
+ const client = withPreflight(mcpClient(TOOLS), k, { toolReceipts: "none" });
114
+ await client.connect(T("https://good.example/mcp"));
115
+ client.preflight.state.receipt.expires_at = new Date(0).toISOString();
116
+ policies["good.example"] = "block";
117
+ await assert.rejects(() => client.callTool({ name: "search" }), McpPreflightRefused);
118
+ await assert.rejects(() => client.callTool({ name: "search" }), McpPreflightRefused);
119
+ assert.deepEqual(client.sent, ["initialize https://good.example/mcp"]);
120
+ assert.equal(k.calls.length, 3);
121
+ });