@intentface/latch-mcp 0.9.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-output.test.js","sourceRoot":"","sources":["../src/host-output.test.ts"],"names":[],"mappings":"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"}
package/dist/host.d.ts ADDED
@@ -0,0 +1,203 @@
1
+ import { type MCPTransport, type OAuthClientProvider } from "@ai-sdk/mcp";
2
+ import type { SingleToolApprovalFunction, ToolApprovalStatus } from "ai";
3
+ import type { SecretResolver } from "@intentface/latch-core";
4
+ import { isHostAllowed } from "@intentface/latch-net";
5
+ import type { FetchFunction } from "./oauth-fetch.js";
6
+ /**
7
+ * The MCP host — connect to MCP servers and expose their tools to an agent,
8
+ * with two Latch-specific guards on top of the SDK client:
9
+ *
10
+ * 1. A per-tenant SSRF allowlist: a connection's host must be explicitly
11
+ * permitted before we open a socket to it (and the transport rejects
12
+ * redirects, so a server can't bounce us somewhere else).
13
+ * 2. Vault-brokered auth: bearer tokens are resolved per-tenant from the Vault
14
+ * and injected as request headers — they never appear in agent config.
15
+ */
16
+ export type McpAuth = {
17
+ type: "bearer";
18
+ vaultKey: string;
19
+ } | {
20
+ type: "headers";
21
+ headers: Record<string, string>;
22
+ };
23
+ /**
24
+ * What the model sees of an MCP tool result, fixed for structured-only servers.
25
+ *
26
+ * The SDK's default `toModelOutput` maps `result.content` to model blocks and
27
+ * only falls back to raw JSON when `content` is missing entirely. Newer MCP
28
+ * servers (Airtable among them) return their payload in `structuredContent`
29
+ * with `content: []` — the spec says they SHOULD mirror it into a text block,
30
+ * but nothing enforces that — and the default then hands the model an empty
31
+ * result while the stored message (what the UI renders) keeps the full object.
32
+ * The agent reports "empty content" for a call that visibly succeeded.
33
+ *
34
+ * When `structuredContent` is present and `content` carries no text, the model
35
+ * gets the structured payload as JSON instead. Everything else keeps the SDK's
36
+ * behavior via `original`.
37
+ */
38
+ export declare function withStructuredContentFallback(original: ((args: never) => unknown) | undefined): (args: {
39
+ toolCallId: string;
40
+ input: unknown;
41
+ output: unknown;
42
+ }) => unknown;
43
+ /** MCP tool annotations (sensitivity hints) the server may advertise. */
44
+ export interface McpToolInfo {
45
+ name: string;
46
+ annotations?: {
47
+ readOnlyHint?: boolean;
48
+ destructiveHint?: boolean;
49
+ idempotentHint?: boolean;
50
+ openWorldHint?: boolean;
51
+ [k: string]: unknown;
52
+ };
53
+ }
54
+ /**
55
+ * Per-CALL approval decision for one tool: evaluated with the model's input
56
+ * each time it calls the tool (synchronously or async — return `boolean` or
57
+ * `Promise<boolean>`), so policy can key on ARGUMENTS. This is the escape
58
+ * hatch for meta-tool servers — one `execute_tool` multiplexing every
59
+ * operation through its arguments carries a single annotation set, so a
60
+ * per-tool boolean can only gate everything or nothing.
61
+ */
62
+ export type McpCallApproval = (input: unknown) => boolean | Promise<boolean>;
63
+ /**
64
+ * Which of a connection's tools require human approval before running.
65
+ * - `"destructive"` (default): respect MCP's built-in hints — gate any tool the
66
+ * server marks `destructiveHint: true` or explicitly `readOnlyHint: false`.
67
+ * - `"all"` / `"none"`: gate every tool / no tool.
68
+ * - a predicate: decide per tool from its annotations. Return a boolean for a
69
+ * whole-tool decision, or an {@link McpCallApproval} —
70
+ * `(input) => boolean | Promise<boolean>` — to decide per call (e.g. gate
71
+ * `execute_tool` only when `input.toolName` starts with `create_`).
72
+ * Annotations stay the first-class signal; the call function is for tools
73
+ * whose annotations structurally can't say enough.
74
+ */
75
+ export type McpApprovalPolicy = "destructive" | "all" | "none" | ((tool: McpToolInfo) => boolean | McpCallApproval);
76
+ /**
77
+ * Apply an approval policy to one tool (defaults to `"destructive"`).
78
+ * A function result means "decide per call with the input" — truthy for
79
+ * gating purposes wherever a whole-tool answer is needed (a policy that must
80
+ * be decided without input is treated conservatively as gated).
81
+ */
82
+ export declare function needsMcpApproval(policy: McpApprovalPolicy | undefined, tool: McpToolInfo): boolean | McpCallApproval;
83
+ /**
84
+ * One tool's value in the AI SDK's `toolApproval` map: an approval status
85
+ * (`"user-approval"` = always gate; `undefined` = not applicable, never gate)
86
+ * or a per-call function the SDK evaluates with the call input.
87
+ */
88
+ export type McpToolApprovalValue = ToolApprovalStatus | SingleToolApprovalFunction<unknown, unknown, unknown>;
89
+ /**
90
+ * Resolve a policy to the value the AI SDK's `toolApproval` map takes for one
91
+ * tool: `"user-approval"` (always gate), `undefined` (never gate), or a
92
+ * per-call function the SDK evaluates with the call input.
93
+ */
94
+ export declare function mcpApprovalValue(policy: McpApprovalPolicy | undefined, tool: McpToolInfo): McpToolApprovalValue;
95
+ /** Tuning for {@link nameHeuristicApproval} — extra verbs merged into the defaults. */
96
+ export interface NameHeuristicOptions {
97
+ /** Verbs that mark a write (added to the built-in write list). */
98
+ writeVerbs?: string[];
99
+ /** Verbs that mark a destructive op (added to the built-in destructive list). */
100
+ destructiveVerbs?: string[];
101
+ }
102
+ /**
103
+ * A generic approval predicate that gates a tool when either its MCP
104
+ * annotations say it's destructive/non-read-only (the same rule
105
+ * {@link needsMcpApproval} applies for `"destructive"`), OR — when the server
106
+ * ships no annotations — the tool's name (normalized to snake_case) starts with
107
+ * a write/destructive verb. Pure function; the returned predicate is stateless.
108
+ *
109
+ * Use it as an `approval` policy (or as a building block) for MCP servers whose
110
+ * tools lack sensitivity hints.
111
+ */
112
+ export declare function nameHeuristicApproval(opts?: NameHeuristicOptions): (tool: McpToolInfo) => boolean;
113
+ /**
114
+ * A connection is either a URL (untrusted/config-driven → SSRF allowlist + auth
115
+ * applies) or a developer-provided transport instance (trusted in-process; e.g.
116
+ * stdio or an in-memory transport — no allowlist, it carries its own auth).
117
+ */
118
+ export type McpConnection = {
119
+ /** Namespacing prefix for this server's tools (e.g. `linear` → `linear_*`). */
120
+ name: string;
121
+ /** MCP server URL (HTTP transport). */
122
+ url: string;
123
+ /** Static/bearer auth. Bearer tokens come from the Vault, per tenant. */
124
+ auth?: McpAuth;
125
+ /** OAuth provider (auto-refreshing). Build per scope via `McpOAuth.provider(scope)`. */
126
+ authProvider?: OAuthClientProvider;
127
+ /**
128
+ * Custom fetch for this connection's requests AND the transport's on-401
129
+ * re-auth/refresh. Pass the same fetch used by the OAuth flow (e.g.
130
+ * `createOAuthDiscoveryFetch`) so discovery normalization is consistent.
131
+ */
132
+ fetch?: FetchFunction;
133
+ /**
134
+ * Permit this connection to reach a private/internal address. By default
135
+ * the host rejects private/loopback/link-local IPs and DNS-rebinding
136
+ * (a name that resolves private) even when the host is on the allowlist.
137
+ * Set `true` only for a trusted local/in-cluster MCP server.
138
+ */
139
+ allowPrivateHost?: boolean;
140
+ /** Which tools require approval (default: respect MCP destructive hints). */
141
+ approval?: McpApprovalPolicy;
142
+ /**
143
+ * Tool visibility filter. When set, a tool for which this returns `false`
144
+ * is dropped entirely — no agent tool, no approval entry. Use it to hide
145
+ * tools an approval predicate can't express (e.g. a hard block). Absent →
146
+ * all of the server's tools are exposed.
147
+ */
148
+ visible?: (tool: McpToolInfo) => boolean;
149
+ } | {
150
+ name: string;
151
+ /** A prebuilt transport (stdio/in-memory/custom). Used as-is. */
152
+ transport: MCPTransport;
153
+ /** Which tools require approval (default: respect MCP destructive hints). */
154
+ approval?: McpApprovalPolicy;
155
+ /**
156
+ * Tool visibility filter. When set, a tool for which this returns `false`
157
+ * is dropped entirely — no agent tool, no approval entry. Use it to hide
158
+ * tools an approval predicate can't express (e.g. a hard block). Absent →
159
+ * all of the server's tools are exposed.
160
+ */
161
+ visible?: (tool: McpToolInfo) => boolean;
162
+ };
163
+ export interface McpHostConfig<P> {
164
+ /** Connections, static or resolved per caller (e.g. to inject OAuth providers). */
165
+ connections: McpConnection[] | ((principal: P) => McpConnection[] | Promise<McpConnection[]>);
166
+ /**
167
+ * SSRF allowlist of permitted hostnames. An entry beginning with `.` matches
168
+ * that domain and its subdomains (`.example.com` → `example.com`,
169
+ * `api.example.com`). Static, or resolved per caller.
170
+ */
171
+ allowedHosts: string[] | ((principal: P) => string[] | Promise<string[]>);
172
+ /** Vault read-side, used for bearer auth. */
173
+ secrets?: SecretResolver<P>;
174
+ /** Observe a connection that failed to open (it's skipped, not fatal). */
175
+ onConnectionError?: (name: string, error: unknown) => void;
176
+ }
177
+ export interface OpenMcp {
178
+ /** Merged, name-prefixed tools, ready to spread into an agent's `tools`. */
179
+ tools: Record<string, unknown>;
180
+ /**
181
+ * Per-tool approval policy derived from each connection's `approval` setting
182
+ * and the MCP servers' tool annotations (`<name>_<tool>: "user-approval"`).
183
+ * Merged into the agent's `toolApproval` by the runtime.
184
+ */
185
+ toolApproval: Record<string, McpToolApprovalValue>;
186
+ /** Close every underlying client. Call when the turn finishes. */
187
+ close(): Promise<void>;
188
+ }
189
+ export interface McpHost<P> {
190
+ open(principal: P): Promise<OpenMcp>;
191
+ }
192
+ export { isHostAllowed };
193
+ /**
194
+ * Resolve the request headers for a connection. Bearer tokens are read from the
195
+ * Vault per tenant; the token is returned for immediate egress use and is never
196
+ * logged or persisted by the host.
197
+ */
198
+ export declare function buildAuthHeaders<P>(conn: {
199
+ name: string;
200
+ auth?: McpAuth;
201
+ }, principal: P, secrets?: SecretResolver<P>): Promise<Record<string, string>>;
202
+ export declare function createMcpHost<P>(config: McpHostConfig<P>): McpHost<P>;
203
+ //# sourceMappingURL=host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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"}
package/dist/host.js ADDED
@@ -0,0 +1,259 @@
1
+ import { createMCPClient, } from "@ai-sdk/mcp";
2
+ import { isBlockedHost, isBlockedResolved, isHostAllowed, } from "@intentface/latch-net";
3
+ /**
4
+ * What the model sees of an MCP tool result, fixed for structured-only servers.
5
+ *
6
+ * The SDK's default `toModelOutput` maps `result.content` to model blocks and
7
+ * only falls back to raw JSON when `content` is missing entirely. Newer MCP
8
+ * servers (Airtable among them) return their payload in `structuredContent`
9
+ * with `content: []` — the spec says they SHOULD mirror it into a text block,
10
+ * but nothing enforces that — and the default then hands the model an empty
11
+ * result while the stored message (what the UI renders) keeps the full object.
12
+ * The agent reports "empty content" for a call that visibly succeeded.
13
+ *
14
+ * When `structuredContent` is present and `content` carries no text, the model
15
+ * gets the structured payload as JSON instead. Everything else keeps the SDK's
16
+ * behavior via `original`.
17
+ */
18
+ export function withStructuredContentFallback(original) {
19
+ return (args) => {
20
+ const result = args.output;
21
+ if (result &&
22
+ typeof result === "object" &&
23
+ !result.isError &&
24
+ result.structuredContent != null &&
25
+ Array.isArray(result.content) &&
26
+ // "No text": empty, or only parts with empty/absent text (some servers
27
+ // pad with a blank text block instead of omitting content).
28
+ result.content.every((part) => !part.text?.trim?.() &&
29
+ part.type === "text")) {
30
+ return { type: "json", value: result.structuredContent };
31
+ }
32
+ return original
33
+ ? original(args)
34
+ : { type: "json", value: args.output };
35
+ };
36
+ }
37
+ /** The `"destructive"` rule: annotations say destructive / not read-only. */
38
+ function destructiveByAnnotations(tool) {
39
+ const a = tool.annotations ?? {};
40
+ return a.destructiveHint === true || a.readOnlyHint === false;
41
+ }
42
+ /**
43
+ * Apply an approval policy to one tool (defaults to `"destructive"`).
44
+ * A function result means "decide per call with the input" — truthy for
45
+ * gating purposes wherever a whole-tool answer is needed (a policy that must
46
+ * be decided without input is treated conservatively as gated).
47
+ */
48
+ export function needsMcpApproval(policy, tool) {
49
+ const p = policy ?? "destructive";
50
+ if (typeof p === "function")
51
+ return p(tool);
52
+ if (p === "all")
53
+ return true;
54
+ if (p === "none")
55
+ return false;
56
+ return destructiveByAnnotations(tool);
57
+ }
58
+ /**
59
+ * Resolve a policy to the value the AI SDK's `toolApproval` map takes for one
60
+ * tool: `"user-approval"` (always gate), `undefined` (never gate), or a
61
+ * per-call function the SDK evaluates with the call input.
62
+ */
63
+ export function mcpApprovalValue(policy, tool) {
64
+ const gate = needsMcpApproval(policy, tool);
65
+ if (typeof gate === "function") {
66
+ // `undefined` is the SDK's "not-applicable" — the call runs ungated.
67
+ return async (input) => (await gate(input)) ? "user-approval" : undefined;
68
+ }
69
+ return gate ? "user-approval" : undefined;
70
+ }
71
+ /** Default verbs a tool name may start with to count as a write. */
72
+ const DEFAULT_WRITE_VERBS = [
73
+ "create",
74
+ "update",
75
+ "delete",
76
+ "write",
77
+ "remove",
78
+ "set",
79
+ "put",
80
+ "post",
81
+ "patch",
82
+ "add",
83
+ "insert",
84
+ "edit",
85
+ "move",
86
+ "assign",
87
+ "send",
88
+ "cancel",
89
+ "complete",
90
+ "upsert",
91
+ "publish",
92
+ "share",
93
+ "invite",
94
+ "revoke",
95
+ "merge",
96
+ "duplicate",
97
+ "copy",
98
+ "archive",
99
+ "destroy",
100
+ "drop",
101
+ "purge",
102
+ ];
103
+ /** Default verbs a tool name may start with to count as destructive. */
104
+ const DEFAULT_DESTRUCTIVE_VERBS = [
105
+ "delete",
106
+ "remove",
107
+ "drop",
108
+ "destroy",
109
+ "archive",
110
+ "purge",
111
+ ];
112
+ /** Normalize CamelCase/PascalCase (`CreateDeal`) → snake_case (`create_deal`). */
113
+ function normalizeToolName(s) {
114
+ return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
115
+ }
116
+ function escapeRegExp(s) {
117
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
118
+ }
119
+ /**
120
+ * A generic approval predicate that gates a tool when either its MCP
121
+ * annotations say it's destructive/non-read-only (the same rule
122
+ * {@link needsMcpApproval} applies for `"destructive"`), OR — when the server
123
+ * ships no annotations — the tool's name (normalized to snake_case) starts with
124
+ * a write/destructive verb. Pure function; the returned predicate is stateless.
125
+ *
126
+ * Use it as an `approval` policy (or as a building block) for MCP servers whose
127
+ * tools lack sensitivity hints.
128
+ */
129
+ export function nameHeuristicApproval(opts) {
130
+ const verbs = Array.from(new Set([
131
+ ...DEFAULT_WRITE_VERBS,
132
+ ...DEFAULT_DESTRUCTIVE_VERBS,
133
+ ...(opts?.writeVerbs ?? []),
134
+ ...(opts?.destructiveVerbs ?? []),
135
+ ]));
136
+ const pattern = new RegExp(`^(${verbs.map(escapeRegExp).join("|")})([_\\W]|$)`, "i");
137
+ return (tool) => {
138
+ const a = tool.annotations ?? {};
139
+ // Annotations are authoritative when present — reuse the destructive rule.
140
+ if (a.readOnlyHint !== undefined || a.destructiveHint !== undefined) {
141
+ return destructiveByAnnotations(tool);
142
+ }
143
+ // No hints — fall back to the tool-name verb heuristic.
144
+ return pattern.test(normalizeToolName(tool.name));
145
+ };
146
+ }
147
+ // Canonical implementation lives in @intentface/latch-net; re-exported here
148
+ // for callers that historically imported it from this package.
149
+ export { isHostAllowed };
150
+ /**
151
+ * Resolve the request headers for a connection. Bearer tokens are read from the
152
+ * Vault per tenant; the token is returned for immediate egress use and is never
153
+ * logged or persisted by the host.
154
+ */
155
+ export async function buildAuthHeaders(conn, principal, secrets) {
156
+ if (!conn.auth)
157
+ return {};
158
+ if (conn.auth.type === "headers")
159
+ return { ...conn.auth.headers };
160
+ // bearer
161
+ if (!secrets) {
162
+ throw new Error(`MCP connection "${conn.name}" needs bearer auth but no secrets resolver is configured`);
163
+ }
164
+ const token = await secrets(principal, conn.auth.vaultKey);
165
+ if (!token) {
166
+ throw new Error(`MCP connection "${conn.name}": secret "${conn.auth.vaultKey}" not found for this tenant`);
167
+ }
168
+ return { Authorization: `Bearer ${token}` };
169
+ }
170
+ export function createMcpHost(config) {
171
+ return {
172
+ async open(principal) {
173
+ const allowed = typeof config.allowedHosts === "function"
174
+ ? await config.allowedHosts(principal)
175
+ : config.allowedHosts;
176
+ const connections = typeof config.connections === "function"
177
+ ? await config.connections(principal)
178
+ : config.connections;
179
+ const clients = [];
180
+ const tools = {};
181
+ const toolApproval = {};
182
+ const closeAll = () => Promise.all(clients.map((c) => c.close().catch(() => { }))).then(() => undefined);
183
+ for (const conn of connections) {
184
+ // SSRF guard is a HARD failure (security): an unlisted host throws,
185
+ // before any socket opens.
186
+ if (!("transport" in conn)) {
187
+ const host = new URL(conn.url).hostname;
188
+ if (!isHostAllowed(host, allowed)) {
189
+ await closeAll();
190
+ throw new Error(`MCP host not allowed for this tenant: ${host} (connection "${conn.name}")`);
191
+ }
192
+ // Defense-in-depth beyond the allowlist: reject private/internal
193
+ // addresses and DNS rebinding (an allowlisted name that resolves to a
194
+ // private/link-local IP — e.g. cloud metadata at 169.254.169.254).
195
+ // Opt out per connection for a trusted local server.
196
+ if (!conn.allowPrivateHost &&
197
+ (isBlockedHost(host) || (await isBlockedResolved(host)))) {
198
+ await closeAll();
199
+ throw new Error(`MCP host resolves to a private/internal address: ${host} (connection "${conn.name}"). Set allowPrivateHost to permit a trusted local server.`);
200
+ }
201
+ }
202
+ // A connection that can't be opened (down / bad token / no tools) is
203
+ // SKIPPED, not fatal — the agent degrades gracefully without it.
204
+ try {
205
+ let client;
206
+ if ("transport" in conn) {
207
+ client = await createMCPClient({
208
+ transport: conn.transport,
209
+ });
210
+ }
211
+ else {
212
+ const headers = await buildAuthHeaders(conn, principal, config.secrets);
213
+ client = await createMCPClient({
214
+ transport: {
215
+ type: "http",
216
+ url: conn.url,
217
+ headers,
218
+ authProvider: conn.authProvider, // OAuth (auto-refresh) if present
219
+ redirect: "error", // no redirect-based SSRF
220
+ fetch: conn.fetch, // normalizing fetch for on-401 re-auth, if set
221
+ },
222
+ });
223
+ }
224
+ clients.push(client);
225
+ // listTools (not tools()) so we keep the annotations the AI SDK's
226
+ // tools() drops — they drive the approval policy. tools() is exactly
227
+ // toolsFromDefinitions(listTools()), so this builds identical tools.
228
+ const defs = await client.listTools();
229
+ const serverTools = client.toolsFromDefinitions(defs);
230
+ const annotations = new Map((defs.tools ?? []).map((t) => [t.name, t.annotations]));
231
+ for (const [name, tool] of Object.entries(serverTools)) {
232
+ const info = { name, annotations: annotations.get(name) };
233
+ // A hidden tool is dropped entirely (no tool, no approval entry) —
234
+ // the primitive for a hard block, which approval can't express.
235
+ if (conn.visible && !conn.visible(info))
236
+ continue;
237
+ const key = `${conn.name}_${name}`; // namespace to avoid collisions
238
+ // Patch the SDK's model-output mapping so structured-only results
239
+ // (structuredContent + empty content) reach the model as JSON
240
+ // instead of an empty block.
241
+ const sdkTool = tool;
242
+ tools[key] = {
243
+ ...sdkTool,
244
+ toModelOutput: withStructuredContentFallback(sdkTool.toModelOutput),
245
+ };
246
+ const approvalValue = mcpApprovalValue(conn.approval, info);
247
+ if (approvalValue !== undefined)
248
+ toolApproval[key] = approvalValue;
249
+ }
250
+ }
251
+ catch (error) {
252
+ config.onConnectionError?.(conn.name, error);
253
+ }
254
+ }
255
+ return { tools, toolApproval, close: closeAll };
256
+ },
257
+ };
258
+ }
259
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @intentface/latch-mcp
3
+ *
4
+ * MCP host for Latch: connect to MCP servers and surface their tools to an
5
+ * agent, behind a per-tenant SSRF allowlist and vault-brokered auth. Built on
6
+ * `@ai-sdk/mcp`; the resulting tools spread straight into an agent's `tools`.
7
+ */
8
+ export { createMcpHost, isHostAllowed, buildAuthHeaders, needsMcpApproval, mcpApprovalValue, nameHeuristicApproval, } from "./host.js";
9
+ export type { McpHost, McpHostConfig, McpConnection, McpAuth, McpApprovalPolicy, McpCallApproval, McpToolApprovalValue, McpToolInfo, NameHeuristicOptions, OpenMcp, } from "./host.js";
10
+ export { createMcpOAuth, createVaultOAuthProvider, decodeOAuthState, } from "./oauth.js";
11
+ export type { McpOAuth, McpOAuthConfig, StartResult } from "./oauth.js";
12
+ export { createOAuthDiscoveryFetch } from "./oauth-fetch.js";
13
+ export type { OAuthDiscoveryFetchOptions, FetchFunction, } from "./oauth-fetch.js";
14
+ export { createMcpConnections, defineMcpConnection, none, bearer, headers, oauth, } from "./connections.js";
15
+ export type { McpConnections, McpConnectionsConfig, McpConnectionDef, McpConnectionAuth, OAuthAuthOptions, ConnectionStatus, } from "./connections.js";
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,OAAO,EACP,aAAa,EACb,aAAa,EACb,OAAO,EACP,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,OAAO,GACR,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,YAAY,EACV,0BAA0B,EAC1B,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,IAAI,EACJ,MAAM,EACN,OAAO,EACP,KAAK,GACN,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @intentface/latch-mcp
3
+ *
4
+ * MCP host for Latch: connect to MCP servers and surface their tools to an
5
+ * agent, behind a per-tenant SSRF allowlist and vault-brokered auth. Built on
6
+ * `@ai-sdk/mcp`; the resulting tools spread straight into an agent's `tools`.
7
+ */
8
+ export { createMcpHost, isHostAllowed, buildAuthHeaders, needsMcpApproval, mcpApprovalValue, nameHeuristicApproval, } from "./host.js";
9
+ export { createMcpOAuth, createVaultOAuthProvider, decodeOAuthState, } from "./oauth.js";
10
+ export { createOAuthDiscoveryFetch } from "./oauth-fetch.js";
11
+ export { createMcpConnections, defineMcpConnection, none, bearer, headers, oauth, } from "./connections.js";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,WAAW,CAAC;AAanB,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAK7D,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,IAAI,EACJ,MAAM,EACN,OAAO,EACP,KAAK,GACN,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * A custom fetch implementation, structurally compatible with the SDK's
3
+ * `FetchFunction` (and `globalThis.fetch`). Declared locally so this package
4
+ * doesn't take a direct dependency on `@ai-sdk/provider-utils`.
5
+ */
6
+ export type FetchFunction = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
7
+ /**
8
+ * A fetch wrapper that canonicalizes a misbehaving MCP provider's OAuth
9
+ * discovery documents so the SDK's strict RFC 8414 checks accept them.
10
+ *
11
+ * `@ai-sdk/mcp` enforces RFC 8414 exactly: the authorization-server metadata's
12
+ * `issuer` must equal the issuer derived from the well-known URL it was fetched
13
+ * from (origin, or origin+path, never with a trailing slash), and there is no
14
+ * provider hook to relax it. Some providers — typically those behind a
15
+ * TLS-terminating proxy — publish discovery documents that violate this:
16
+ *
17
+ * • the protected-resource doc advertises its `authorization_servers` (and
18
+ * `resource`) over `http://` even though the live endpoints are `https://`;
19
+ * • the authorization-server doc's `issuer` carries a trailing slash
20
+ * (`https://host/`) while the SDK expects the bare origin (`https://host`).
21
+ *
22
+ * Either makes `auth()` throw `... issuer ... does not match expected issuer ...`.
23
+ * This wrapper rewrites only the discovery documents (by well-known path), only
24
+ * for the configured hosts, leaving every other request untouched. Wire it into
25
+ * BOTH the OAuth flow (`McpOAuthConfig.fetch`) and the host transport
26
+ * (`McpConnection.fetch`) so the on-401 re-auth/refresh uses it too.
27
+ */
28
+ export interface OAuthDiscoveryFetchOptions {
29
+ /** Underlying fetch. Default: `globalThis.fetch`. */
30
+ baseFetch?: FetchFunction;
31
+ /**
32
+ * Force this scheme on the URLs inside discovery documents (issuer,
33
+ * authorization/token/registration endpoints, advertised resource and
34
+ * authorization servers). Use `"https"` for the common proxied-http case.
35
+ * Omit to leave schemes as published.
36
+ */
37
+ forceScheme?: "https" | "http";
38
+ /**
39
+ * Restrict normalization to these hostnames — a safety boundary so a third
40
+ * party's metadata is never rewritten. Omit to apply to any host.
41
+ */
42
+ hosts?: string[];
43
+ }
44
+ export declare function createOAuthDiscoveryFetch(options?: OAuthDiscoveryFetchOptions): FetchFunction;
45
+ //# sourceMappingURL=oauth-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-fetch.d.ts","sourceRoot":"","sources":["../src/oauth-fetch.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,CAAC,EAAE,WAAW,KACf,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,0BAA0B;IACzC,qDAAqD;IACrD,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAqED,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,0BAA+B,GACvC,aAAa,CAkDf"}