@intentface/latch-mcp 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentface/latch-mcp",
3
- "version": "0.17.1",
3
+ "version": "0.18.0",
4
4
  "description": "Latch MCP host — connect to MCP servers as tools, with a per-tenant SSRF allowlist and vault-brokered auth.",
5
5
  "keywords": [
6
6
  "ai",
@@ -31,8 +31,8 @@
31
31
  "registry": "https://registry.npmjs.org"
32
32
  },
33
33
  "dependencies": {
34
- "@intentface/latch-core": "^0.17.1",
35
- "@intentface/latch-net": "^0.17.1"
34
+ "@intentface/latch-core": "^0.18.0",
35
+ "@intentface/latch-net": "^0.18.0"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@ai-sdk/mcp": ">=2.0.0",
@@ -50,7 +50,7 @@
50
50
  "directory": "packages/mcp"
51
51
  },
52
52
  "scripts": {
53
- "build": "tsc -p tsconfig.json",
53
+ "build": "tsc -p tsconfig.build.json",
54
54
  "typecheck": "tsc -p tsconfig.json --noEmit",
55
55
  "test": "vitest run"
56
56
  }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=approval.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"approval.test.d.ts","sourceRoot":"","sources":["../src/approval.test.ts"],"names":[],"mappings":""}
@@ -1,64 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { mcpApprovalValue, needsMcpApproval, nameHeuristicApproval, } from "./index.js";
3
- /**
4
- * The approval policy's two-level shape: a policy decides per TOOL (from its
5
- * descriptor/annotations), and may hand back a per-CALL function that decides
6
- * from the model's arguments. The call form exists for meta-tool servers —
7
- * one `execute_tool` multiplexing every operation carries a single annotation
8
- * set, so a per-tool boolean can only gate everything or nothing.
9
- */
10
- const READ_TOOL = {
11
- name: "find_many_companies",
12
- annotations: { readOnlyHint: true },
13
- };
14
- const WRITE_TOOL = {
15
- name: "create_company",
16
- annotations: { destructiveHint: true },
17
- };
18
- const META_TOOL = { name: "execute_tool" };
19
- describe("needsMcpApproval", () => {
20
- it('"destructive" (default) keys on annotations', () => {
21
- expect(needsMcpApproval(undefined, READ_TOOL)).toBe(false);
22
- expect(needsMcpApproval(undefined, WRITE_TOOL)).toBe(true);
23
- expect(needsMcpApproval("destructive", META_TOOL)).toBe(false); // no hints
24
- });
25
- it("passes a per-call function through from the predicate", () => {
26
- const perCall = (input) => String(input?.toolName ?? "").startsWith("create_");
27
- const gate = needsMcpApproval((tool) => (tool.name === "execute_tool" ? perCall : false), META_TOOL);
28
- expect(gate).toBe(perCall);
29
- });
30
- });
31
- describe("mcpApprovalValue — the SDK toolApproval map value", () => {
32
- it("boolean policies map to 'user-approval' / undefined", () => {
33
- expect(mcpApprovalValue("all", READ_TOOL)).toBe("user-approval");
34
- expect(mcpApprovalValue("none", WRITE_TOOL)).toBeUndefined();
35
- expect(mcpApprovalValue(undefined, WRITE_TOOL)).toBe("user-approval");
36
- expect(mcpApprovalValue(undefined, READ_TOOL)).toBeUndefined();
37
- });
38
- it("a per-call policy gates by ARGUMENTS at call time", async () => {
39
- const value = mcpApprovalValue((tool) => tool.name === "execute_tool"
40
- ? (input) => /^(create|update|delete)_/.test(String(input?.toolName ?? ""))
41
- : false, META_TOOL);
42
- expect(typeof value).toBe("function");
43
- const decide = value;
44
- // The read that finding 1.2 saw gated — through the meta-tool — runs free…
45
- expect(await decide({ toolName: "find_many_companies" })).toBeUndefined();
46
- // …while the mutating operations still stop for a human.
47
- expect(await decide({ toolName: "create_person" })).toBe("user-approval");
48
- expect(await decide({ toolName: "delete_company" })).toBe("user-approval");
49
- });
50
- it("supports an async per-call decision", async () => {
51
- const value = mcpApprovalValue(() => async () => true, META_TOOL);
52
- const decide = value;
53
- expect(await decide({})).toBe("user-approval");
54
- });
55
- });
56
- describe("nameHeuristicApproval (unchanged behavior)", () => {
57
- it("annotations stay authoritative; verb heuristic only without hints", () => {
58
- const policy = nameHeuristicApproval();
59
- expect(policy(READ_TOOL)).toBe(false); // readOnlyHint wins
60
- expect(policy({ name: "create_deal" })).toBe(true); // verb, no hints
61
- expect(policy({ name: "list_deals" })).toBe(false);
62
- });
63
- });
64
- //# sourceMappingURL=approval.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"approval.test.js","sourceRoot":"","sources":["../src/approval.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=connections-auth.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"connections-auth.test.d.ts","sourceRoot":"","sources":["../src/connections-auth.test.ts"],"names":[],"mappings":""}
@@ -1,34 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { bearer, bearerHeader } from "./index.js";
3
- /**
4
- * `bearer` is the one auth strategy whose header is COMPUTED per caller. The
5
- * default is the OAuth-style `Authorization: Bearer <token>`; a server keyed on
6
- * its own header (`X-API-Key: <key>`) names it explicitly. Pinned here because
7
- * the header is assembled at egress, out of sight of any integration test.
8
- */
9
- describe("bearerHeader", () => {
10
- it("defaults to Authorization: Bearer <token>", () => {
11
- expect(bearerHeader({}, "tok")).toEqual({ Authorization: "Bearer tok" });
12
- });
13
- it("sends the token in a custom header with no prefix", () => {
14
- expect(bearerHeader({ header: "X-API-Key", prefix: "" }, "k1")).toEqual({
15
- "X-API-Key": "k1",
16
- });
17
- });
18
- it("keeps a custom prefix on the default header", () => {
19
- expect(bearerHeader({ prefix: "Token " }, "abc")).toEqual({
20
- Authorization: "Token abc",
21
- });
22
- });
23
- });
24
- describe("bearer()", () => {
25
- it("omits header/prefix unless given, so the defaults apply", () => {
26
- const auth = bearer({ getToken: () => "t" });
27
- expect(auth).toEqual({ kind: "bearer", getToken: expect.any(Function) });
28
- });
29
- it("carries header/prefix through to the strategy", () => {
30
- const auth = bearer({ getToken: () => "t", header: "X-API-Key", prefix: "" });
31
- expect(auth).toMatchObject({ kind: "bearer", header: "X-API-Key", prefix: "" });
32
- });
33
- });
34
- //# sourceMappingURL=connections-auth.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"connections-auth.test.js","sourceRoot":"","sources":["../src/connections-auth.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAElD;;;;;GAKG;AACH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,YAAY,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;YACtE,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;YACxD,aAAa,EAAE,WAAW;SAC3B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;IACxB,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;QAC9E,MAAM,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=guarded-fetch.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"guarded-fetch.test.d.ts","sourceRoot":"","sources":["../src/guarded-fetch.test.ts"],"names":[],"mappings":""}
@@ -1,153 +0,0 @@
1
- /**
2
- * The fetch behind every MCP request — transport and OAuth alike.
3
- *
4
- * What is asserted here is the difference between "we checked the hostname"
5
- * and "we connected to the address we checked": the vetting happens inside the
6
- * lookup the socket itself uses, so a name cannot answer public to the check
7
- * and private to the connect. Plus the two things this wrapper adds on top of
8
- * the pinned fetch — redirects refused, and a `Request` unpacked rather than
9
- * handed to undici, which rejects one built by another fetch implementation.
10
- */
11
- import { createServer } from "node:http";
12
- import { afterEach, describe, expect, it } from "vitest";
13
- import { createGuardedFetch } from "./guarded-fetch.js";
14
- const A = (address, family = 4) => ({ address, family });
15
- const cleanups = [];
16
- afterEach(async () => {
17
- while (cleanups.length)
18
- await cleanups.pop()();
19
- });
20
- function guarded(...args) {
21
- const f = createGuardedFetch(...args);
22
- cleanups.push(() => f.close());
23
- return f;
24
- }
25
- async function listen(server) {
26
- await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
27
- cleanups.push(() => new Promise((resolve) => server.close(resolve)));
28
- return server.address().port;
29
- }
30
- /** A lookup backed by a mutable table — no real DNS in any test. */
31
- const tableLookup = (table) => (hostname, _options, cb) => {
32
- const addrs = table[hostname];
33
- if (!addrs)
34
- return cb(Object.assign(new Error(`ENOTFOUND ${hostname}`), { code: "ENOTFOUND" }));
35
- cb(null, addrs);
36
- };
37
- async function rejection(p) {
38
- try {
39
- await p;
40
- }
41
- catch (err) {
42
- const parts = [];
43
- const walk = (e) => {
44
- if (!e)
45
- return;
46
- if (e instanceof AggregateError)
47
- e.errors.forEach(walk);
48
- if (e instanceof Error) {
49
- parts.push(e.message);
50
- walk(e.cause);
51
- }
52
- };
53
- walk(err);
54
- return parts.join(" <- ");
55
- }
56
- throw new Error("expected the fetch to reject");
57
- }
58
- describe("address vetting", () => {
59
- it("refuses a name that resolves to an internal address", async () => {
60
- // The cloud metadata endpoint, reached through a name the MCP server's
61
- // metadata could perfectly well have pointed at.
62
- const f = guarded({
63
- lookup: tableLookup({ "auth.evil.example": [A("169.254.169.254")] }),
64
- });
65
- expect(await rejection(f("http://auth.evil.example/.well-known/openid-configuration"))).toMatch(/Blocked private\/internal address: auth\.evil\.example -> 169\.254\.169\.254/);
66
- });
67
- it("refuses a name that answers public first and private when the socket opens", async () => {
68
- // DNS rebinding: the open-time check would pass, the connect would land
69
- // inside. The vetting is in the lookup the socket uses, so there is no gap.
70
- const table = { "rebind.example": [A("93.184.216.34")] };
71
- const f = guarded({ lookup: tableLookup(table) });
72
- table["rebind.example"] = [A("127.0.0.1")];
73
- expect(await rejection(f("http://rebind.example/mcp"))).toMatch(/Blocked private\/internal address: rebind\.example -> 127\.0\.0\.1/);
74
- });
75
- it("permits a loopback server when the connection opted in", async () => {
76
- // A developer's `http://localhost:3999/mcp`, and an in-cluster server.
77
- const port = await listen(createServer((_req, res) => {
78
- res.writeHead(200, { "content-type": "application/json" });
79
- res.end('{"ok":true}');
80
- }));
81
- const f = guarded({ allowPrivateHost: true });
82
- const res = await f(`http://127.0.0.1:${port}/mcp`);
83
- expect(res.status).toBe(200);
84
- expect(await res.json()).toEqual({ ok: true });
85
- });
86
- });
87
- describe("what the wrapper adds", () => {
88
- it("refuses to follow a redirect, even when the caller asks for one", async () => {
89
- // A 302 names an address nothing vetted; no MCP or OAuth leg needs one.
90
- const port = await listen(createServer((_req, res) => {
91
- res.writeHead(302, { location: "http://169.254.169.254/latest/meta-data/" });
92
- res.end();
93
- }));
94
- const f = guarded({ allowPrivateHost: true });
95
- expect(await rejection(f(`http://127.0.0.1:${port}/`, { redirect: "follow" }))).toMatch(/redirect/i);
96
- });
97
- it("carries the Request's abort signal, so a cancelled call releases its socket", async () => {
98
- // Without this the reconstruction drops `signal` and an aborted MCP or
99
- // OAuth call keeps its socket until the server finally answers.
100
- let release;
101
- const held = new Promise((resolve) => (release = resolve));
102
- cleanups.push(() => release());
103
- const port = await listen(createServer(async (_req, res) => {
104
- await held; // never answers until the test lets it
105
- res.writeHead(200);
106
- res.end("late");
107
- }));
108
- const f = guarded({ allowPrivateHost: true });
109
- const controller = new AbortController();
110
- const inflight = f(new Request(`http://127.0.0.1:${port}/slow`, { signal: controller.signal }));
111
- controller.abort();
112
- expect(await rejection(inflight)).toMatch(/abort/i);
113
- });
114
- it("an explicit init signal replaces the Request's", async () => {
115
- const port = await listen(createServer((_req, res) => {
116
- res.writeHead(200);
117
- res.end("ok");
118
- }));
119
- const f = guarded({ allowPrivateHost: true });
120
- const requestSide = new AbortController();
121
- requestSide.abort(); // already aborted — must NOT decide the outcome
122
- const res = await f(new Request(`http://127.0.0.1:${port}/`, { signal: requestSide.signal }), {
123
- signal: new AbortController().signal,
124
- });
125
- expect(res.status).toBe(200);
126
- });
127
- it("unpacks a Request rather than handing it to undici", async () => {
128
- // undici rejects a Request built by another fetch implementation
129
- // (cross-realm brand check), so a caller passing one must not silently
130
- // bypass the guard — method, headers and body have to survive.
131
- let seen = {};
132
- const port = await listen(createServer((req, res) => {
133
- let body = "";
134
- req.on("data", (c) => (body += c));
135
- req.on("end", () => {
136
- seen = { method: req.method, auth: req.headers.authorization, body };
137
- res.writeHead(200, { "content-type": "application/json" });
138
- res.end('{"ok":true}');
139
- });
140
- }));
141
- const f = guarded({ allowPrivateHost: true });
142
- const res = await f(new Request(`http://127.0.0.1:${port}/token`, {
143
- method: "POST",
144
- headers: { authorization: "Bearer t0ken", "content-type": "application/json" },
145
- body: '{"grant_type":"authorization_code"}',
146
- }));
147
- expect(res.status).toBe(200);
148
- expect(seen.method).toBe("POST");
149
- expect(seen.auth).toBe("Bearer t0ken");
150
- expect(seen.body).toBe('{"grant_type":"authorization_code"}');
151
- });
152
- });
153
- //# sourceMappingURL=guarded-fetch.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"guarded-fetch.test.js","sourceRoot":"","sources":["../src/guarded-fetch.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=host-output.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"host-output.test.d.ts","sourceRoot":"","sources":["../src/host-output.test.ts"],"names":[],"mappings":""}
@@ -1,51 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { withStructuredContentFallback } from "./host.js";
3
- const call = (output, original) => withStructuredContentFallback(original)({ toolCallId: "c1", input: {}, output });
4
- describe("withStructuredContentFallback", () => {
5
- it("returns structuredContent as JSON when content is empty (Airtable-style)", () => {
6
- const output = {
7
- content: [],
8
- structuredContent: { bases: [{ id: "app1", name: "ATS" }] },
9
- isError: false,
10
- };
11
- expect(call(output)).toEqual({
12
- type: "json",
13
- value: { bases: [{ id: "app1", name: "ATS" }] },
14
- });
15
- });
16
- it("falls back when content only holds blank text parts", () => {
17
- const output = {
18
- content: [{ type: "text", text: " " }],
19
- structuredContent: { ok: true },
20
- };
21
- expect(call(output)).toEqual({ type: "json", value: { ok: true } });
22
- });
23
- it("keeps the SDK mapping when content carries real text", () => {
24
- const output = {
25
- content: [{ type: "text", text: "3 bases found" }],
26
- structuredContent: { bases: [] },
27
- };
28
- const original = () => ({ type: "content", value: [{ type: "text", text: "3 bases found" }] });
29
- expect(call(output, original)).toEqual({
30
- type: "content",
31
- value: [{ type: "text", text: "3 bases found" }],
32
- });
33
- });
34
- it("keeps the SDK mapping for non-text parts (images)", () => {
35
- const output = {
36
- content: [{ type: "image", data: "…", mimeType: "image/png" }],
37
- structuredContent: { alt: "chart" },
38
- };
39
- const original = () => "sdk";
40
- expect(call(output, original)).toBe("sdk");
41
- });
42
- it("leaves errors alone even with structuredContent present", () => {
43
- const output = { content: [], structuredContent: { reason: "x" }, isError: true };
44
- const original = () => "sdk-error-path";
45
- expect(call(output, original)).toBe("sdk-error-path");
46
- });
47
- it("passes plain results through as JSON when no original mapper exists", () => {
48
- expect(call({ anything: 1 })).toEqual({ type: "json", value: { anything: 1 } });
49
- });
50
- });
51
- //# sourceMappingURL=host-output.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"host-output.test.js","sourceRoot":"","sources":["../src/host-output.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=oauth-fetch.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"oauth-fetch.test.d.ts","sourceRoot":"","sources":["../src/oauth-fetch.test.ts"],"names":[],"mappings":""}
@@ -1,25 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createOAuthDiscoveryFetch } from "./oauth-fetch.js";
3
- /** A fetch that serves one discovery document for every URL. */
4
- const serving = (doc) => async () => new Response(JSON.stringify(doc), { status: 200, headers: { "content-type": "application/json" } });
5
- describe("createOAuthDiscoveryFetch", () => {
6
- it("forces https on a proxied server's discovery, and strips the issuer's trailing slash", async () => {
7
- const f = createOAuthDiscoveryFetch({
8
- forceScheme: "https",
9
- baseFetch: serving({ issuer: "http://mcp.example.com/", token_endpoint: "http://mcp.example.com/token" }),
10
- });
11
- const doc = await (await f("https://mcp.example.com/.well-known/oauth-authorization-server")).json();
12
- expect(doc).toMatchObject({ issuer: "https://mcp.example.com", token_endpoint: "https://mcp.example.com/token" });
13
- });
14
- it.each(["localhost", "127.0.0.1", "[::1]"])("leaves a local server's http scheme alone (%s)", async (host) => {
15
- const f = createOAuthDiscoveryFetch({
16
- forceScheme: "https",
17
- baseFetch: serving({ resource: `http://${host}:3011/mcp`, authorization_servers: [`http://${host}:3011/api/auth/`] }),
18
- });
19
- const doc = await (await f(`http://${host}:3011/.well-known/oauth-protected-resource/mcp`)).json();
20
- // The SDK compares `resource` with the URL it connected to; https here broke every local server.
21
- expect(doc.resource).toBe(`http://${host}:3011/mcp`);
22
- expect(doc.authorization_servers[0]).toMatch(/^http:\/\//);
23
- });
24
- });
25
- //# sourceMappingURL=oauth-fetch.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"oauth-fetch.test.js","sourceRoot":"","sources":["../src/oauth-fetch.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,gEAAgE;AAChE,MAAM,OAAO,GAAG,CAAC,GAAY,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAC3C,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC;AAEtG,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,sFAAsF,EAAE,KAAK,IAAI,EAAE;QACpG,MAAM,CAAC,GAAG,yBAAyB,CAAC;YAClC,WAAW,EAAE,OAAO;YACpB,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,yBAAyB,EAAE,cAAc,EAAE,8BAA8B,EAAE,CAAC;SAC1G,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,gEAAgE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACrG,MAAM,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,yBAAyB,EAAE,cAAc,EAAE,+BAA+B,EAAE,CAAC,CAAC;IACpH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,gDAAgD,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAC5G,MAAM,CAAC,GAAG,yBAAyB,CAAC;YAClC,WAAW,EAAE,OAAO;YACpB,SAAS,EAAE,OAAO,CAAC,EAAE,QAAQ,EAAE,UAAU,IAAI,WAAW,EAAE,qBAAqB,EAAE,CAAC,UAAU,IAAI,iBAAiB,CAAC,EAAE,CAAC;SACtH,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,UAAU,IAAI,gDAAgD,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACnG,iGAAiG;QACjG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,IAAI,WAAW,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=oauth.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"oauth.test.d.ts","sourceRoot":"","sources":["../src/oauth.test.ts"],"names":[],"mappings":""}
@@ -1,275 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach } from "vitest";
2
- /**
3
- * `scope` may be a function, resolved per caller at flow time.
4
- *
5
- * This is a correctness requirement rather than API sugar: a single `McpOAuth`
6
- * is shared across callers (UI-added connections are cached by connection
7
- * NAME, while the connection record — and therefore its requested scopes —
8
- * lives in each caller's own Vault). A static scope string would send one
9
- * caller's scopes on another caller's authorize request, which for a
10
- * *restriction* scope like Loredex's `private_only` means silently handing
11
- * someone a broader grant than their connection asked for.
12
- */
13
- const authMock = vi.hoisted(() => vi.fn());
14
- vi.mock("@ai-sdk/mcp", () => ({
15
- auth: authMock,
16
- }));
17
- const { createMcpOAuth, decodeOAuthState } = await import("./oauth.js");
18
- /** Minimal in-memory Vault, keyed by principal so callers stay separate. */
19
- function fakeVault() {
20
- const store = new Map();
21
- const k = (p, key) => `${p.userId}::${key}`;
22
- return {
23
- async set(p, key, value) {
24
- store.set(k(p, key), value);
25
- },
26
- async get(p, key) {
27
- return store.get(k(p, key));
28
- },
29
- async has(p, key) {
30
- return store.has(k(p, key));
31
- },
32
- async delete(p, key) {
33
- store.delete(k(p, key));
34
- },
35
- async list(p) {
36
- return [...store.keys()].filter((x) => x.startsWith(`${p.userId}::`));
37
- },
38
- resolver: {},
39
- _store: store,
40
- };
41
- }
42
- function build(scope, vault = fakeVault()) {
43
- return {
44
- vault,
45
- oauth: createMcpOAuth({
46
- vault: vault,
47
- config: {
48
- name: "loredex",
49
- serverUrl: "https://loredex.example/ws/mcp",
50
- redirectUrl: "https://latch.example/cb",
51
- clientMetadata: {
52
- client_name: "Latch (loredex)",
53
- redirect_uris: ["https://latch.example/cb"],
54
- },
55
- scope: scope,
56
- },
57
- }),
58
- };
59
- }
60
- /** The `scope` value handed to the SDK on the most recent `start()`. */
61
- const lastScope = () => authMock.mock.calls.at(-1)?.[1]?.scope;
62
- beforeEach(() => {
63
- authMock.mockReset();
64
- authMock.mockResolvedValue("AUTHORIZED");
65
- });
66
- describe("createMcpOAuth scope resolution", () => {
67
- it("passes a static scope through unchanged", async () => {
68
- const { oauth } = build("private_only");
69
- await oauth.start({ userId: "u1" });
70
- expect(lastScope()).toBe("private_only");
71
- });
72
- it("omits the scope entirely when unset", async () => {
73
- // Connections that request nothing must keep their previous, scope-less
74
- // authorize request — adding an empty `scope=` would change the ask.
75
- const { oauth } = build(undefined);
76
- await oauth.start({ userId: "u1" });
77
- expect(lastScope()).toBeUndefined();
78
- });
79
- it("resolves a function scope per caller", async () => {
80
- // The core requirement: two callers of the SAME connection get their own
81
- // scopes, because the record is per-caller while the client is shared.
82
- const scopes = {
83
- u1: "private_only",
84
- u2: undefined,
85
- };
86
- const { oauth } = build((p) => scopes[p.userId]);
87
- await oauth.start({ userId: "u1" });
88
- expect(lastScope()).toBe("private_only");
89
- await oauth.start({ userId: "u2" });
90
- expect(lastScope()).toBeUndefined();
91
- // ...and back again, so nothing was cached from the first resolution.
92
- await oauth.start({ userId: "u1" });
93
- expect(lastScope()).toBe("private_only");
94
- });
95
- it("awaits an async scope resolver", async () => {
96
- // The real resolver reads the connection out of the Vault, so it is async.
97
- const { oauth } = build(async (p) => p.userId === "u1" ? "mcp:read private_only" : undefined);
98
- await oauth.start({ userId: "u1" });
99
- expect(lastScope()).toBe("mcp:read private_only");
100
- });
101
- });
102
- describe("authorizationParams", () => {
103
- // Providers like Google need extra authorize params the SDK doesn't model
104
- // (`access_type=offline&prompt=consent` for a refresh token). They are
105
- // appended to the SDK-built redirect URL in start().
106
- it("appends the configured params to the authorization URL", async () => {
107
- const vault = fakeVault();
108
- const oauth = createMcpOAuth({
109
- vault: vault,
110
- config: {
111
- name: "gcal",
112
- serverUrl: "https://calendarmcp.example/mcp/v1",
113
- redirectUrl: "https://latch.example/cb",
114
- clientMetadata: {
115
- client_name: "Latch (gcal)",
116
- redirect_uris: ["https://latch.example/cb"],
117
- },
118
- authorizationParams: { access_type: "offline", prompt: "consent" },
119
- },
120
- });
121
- // Simulate the SDK asking for a browser redirect.
122
- authMock.mockImplementationOnce(async (provider) => {
123
- provider.redirectToAuthorization(new URL("https://idp.example/authorize?client_id=abc"));
124
- return "REDIRECT";
125
- });
126
- const res = await oauth.start({ userId: "u1" });
127
- if (res.status !== "redirect")
128
- throw new Error("expected redirect");
129
- const url = new URL(res.authorizationUrl);
130
- expect(url.searchParams.get("client_id")).toBe("abc"); // SDK params kept
131
- expect(url.searchParams.get("access_type")).toBe("offline");
132
- expect(url.searchParams.get("prompt")).toBe("consent");
133
- });
134
- });
135
- describe("state encoding", () => {
136
- it("round-trips app context (incl. a pinned principal) through decodeOAuthState", async () => {
137
- // The handler pins the resolved principal into the flow state at
138
- // authorize time; the callback must recover it byte-for-byte.
139
- const { oauth } = build(undefined);
140
- const stateData = {
141
- returnTo: "/chat/c1",
142
- __latch: { p: { orgId: "org_a", userId: "u1" }, iat: 1_700_000_000_000 },
143
- };
144
- let sent;
145
- authMock.mockImplementationOnce(async (provider) => {
146
- sent = provider.state();
147
- await provider.saveState(sent);
148
- provider.redirectToAuthorization(new URL("https://idp.example/authorize"));
149
- return "REDIRECT";
150
- });
151
- await oauth.start({ userId: "u1" }, { state: stateData });
152
- expect(decodeOAuthState(sent)).toEqual(stateData);
153
- });
154
- it("returns undefined for a bare csrf or garbage", () => {
155
- expect(decodeOAuthState("deadbeef")).toBeUndefined();
156
- expect(decodeOAuthState("deadbeef.%%%not-b64%%%")).toBeUndefined();
157
- expect(decodeOAuthState(null)).toBeUndefined();
158
- });
159
- });
160
- describe("finish", () => {
161
- it("deletes the spent state + verifier but keeps tokens/client/as_info", async () => {
162
- // Single-use flow state: a replayed callback URL must find no stored flow
163
- // material — while the durable credential parts survive.
164
- const { vault, oauth } = build(undefined);
165
- const p = { userId: "u1" };
166
- for (const part of ["tokens", "verifier", "state", "client", "as_info"]) {
167
- await vault.set(p, `mcp_oauth:loredex:${part}`, "x");
168
- }
169
- authMock.mockResolvedValueOnce("AUTHORIZED");
170
- await oauth.finish(p, { code: "abc", state: "deadbeef" });
171
- expect(await vault.has(p, "mcp_oauth:loredex:state")).toBe(false);
172
- expect(await vault.has(p, "mcp_oauth:loredex:verifier")).toBe(false);
173
- expect(await vault.has(p, "mcp_oauth:loredex:tokens")).toBe(true);
174
- expect(await vault.has(p, "mcp_oauth:loredex:client")).toBe(true);
175
- expect(await vault.has(p, "mcp_oauth:loredex:as_info")).toBe(true);
176
- });
177
- it("does not delete flow state when the exchange throws", async () => {
178
- // A transient exchange failure must leave the flow re-tryable.
179
- const { vault, oauth } = build(undefined);
180
- const p = { userId: "u1" };
181
- await vault.set(p, "mcp_oauth:loredex:state", "s");
182
- await vault.set(p, "mcp_oauth:loredex:verifier", "v");
183
- authMock.mockRejectedValueOnce(new Error("exchange failed"));
184
- await expect(oauth.finish(p, { code: "abc", state: "s" })).rejects.toThrow();
185
- expect(await vault.has(p, "mcp_oauth:loredex:state")).toBe(true);
186
- expect(await vault.has(p, "mcp_oauth:loredex:verifier")).toBe(true);
187
- });
188
- });
189
- describe("invalidateCredentials", () => {
190
- it("clears the DCR client as well as the tokens", async () => {
191
- // A scope change re-runs registration: the stored client was registered
192
- // under the old scope, so keeping it would re-request the old grant.
193
- const { vault, oauth } = build("private_only");
194
- const p = { userId: "u1" };
195
- for (const part of ["tokens", "verifier", "state", "client", "as_info"]) {
196
- await vault.set(p, `mcp_oauth:loredex:${part}`, "x");
197
- }
198
- await oauth.provider(p).invalidateCredentials?.("all");
199
- expect(await vault.list(p)).toEqual([]);
200
- });
201
- it("leaves another caller's tokens alone", async () => {
202
- const { vault, oauth } = build("private_only");
203
- const mine = { userId: "u1" };
204
- const theirs = { userId: "u2" };
205
- await vault.set(mine, "mcp_oauth:loredex:tokens", "mine");
206
- await vault.set(theirs, "mcp_oauth:loredex:tokens", "theirs");
207
- await oauth.provider(mine).invalidateCredentials?.("all");
208
- expect(await vault.get(theirs, "mcp_oauth:loredex:tokens")).toBe("theirs");
209
- });
210
- });
211
- /**
212
- * The authorization-server allowlist.
213
- *
214
- * An MCP server's protected-resource metadata NAMES its authorization server,
215
- * and Latch then fetches that server's metadata, registers a client with it and
216
- * posts the code to it. Unbounded, that is "connect to whatever address the
217
- * server operator writes down" — an SSRF whose errors carry the response back
218
- * to whoever added the connection. So the bound defaults to the MCP server's
219
- * own origin, and delegating elsewhere has to be declared.
220
- */
221
- describe("authorization-server allowlist", () => {
222
- const principal = { userId: "u1" };
223
- const serverUrl = "https://loredex.example/ws/mcp";
224
- function providerFor(allowedAuthorizationServers) {
225
- const vault = fakeVault();
226
- return createMcpOAuth({
227
- vault: vault,
228
- config: {
229
- name: "loredex",
230
- serverUrl,
231
- redirectUrl: "https://latch.example/cb",
232
- clientMetadata: {
233
- client_name: "Latch (loredex)",
234
- redirect_uris: ["https://latch.example/cb"],
235
- },
236
- ...(allowedAuthorizationServers ? { allowedAuthorizationServers } : {}),
237
- },
238
- }).provider(principal);
239
- }
240
- const validate = (p, as) => p.validateAuthorizationServerURL(serverUrl, as);
241
- it("undeclared, an authorization server on another origin is refused", () => {
242
- // The whole attack in one line: the metadata names an internal address.
243
- expect(() => validate(providerFor(), "http://10.0.0.7:8500/register")).toThrow(/authorization server http:\/\/10\.0\.0\.7:8500 is not allowed/);
244
- });
245
- it("undeclared, the MCP server's own origin is allowed", () => {
246
- // The common case — a server that is its own authorization server — keeps
247
- // working with no host configuration at all.
248
- expect(() => validate(providerFor(), "https://loredex.example/oauth/authorize")).not.toThrow();
249
- });
250
- it("a declared third-party authorization server is allowed", () => {
251
- const p = providerFor(["https://accounts.google.com"]);
252
- expect(() => validate(p, "https://accounts.google.com/o/oauth2/v2/auth")).not.toThrow();
253
- // …and declaring one does not re-admit everything else.
254
- expect(() => validate(p, "http://169.254.169.254/")).toThrow(/is not allowed/);
255
- });
256
- it("an empty list allows nothing, rather than everything", () => {
257
- // The old reading of `[]` was "not configured → allow any", which is how
258
- // the guard came to be dead code.
259
- expect(() => validate(providerFor([]), "https://loredex.example/oauth/authorize")).toThrow(/is not allowed/);
260
- });
261
- it("the refusal names the origin and quotes no response body", () => {
262
- let message = "";
263
- try {
264
- validate(providerFor(), "http://10.0.0.7:8500/register");
265
- }
266
- catch (e) {
267
- message = e.message;
268
- }
269
- expect(message).toContain("http://10.0.0.7:8500");
270
- expect(message).toContain("allowedAuthorizationServers");
271
- // The SDK's own errors embed `Raw body: …`; ours must never grow one.
272
- expect(message).not.toMatch(/raw body|<html|\{"error"/i);
273
- });
274
- });
275
- //# sourceMappingURL=oauth.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"oauth.test.js","sourceRoot":"","sources":["../src/oauth.test.ts"],"names":[],"mappings":"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"}