@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.
- package/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/approval.test.d.ts +2 -0
- package/dist/approval.test.d.ts.map +1 -0
- package/dist/approval.test.js +64 -0
- package/dist/approval.test.js.map +1 -0
- package/dist/connections.d.ts +180 -0
- package/dist/connections.d.ts.map +1 -0
- package/dist/connections.js +255 -0
- package/dist/connections.js.map +1 -0
- package/dist/host-output.test.d.ts +2 -0
- package/dist/host-output.test.d.ts.map +1 -0
- package/dist/host-output.test.js +51 -0
- package/dist/host-output.test.js.map +1 -0
- package/dist/host.d.ts +203 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +259 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth-fetch.d.ts +45 -0
- package/dist/oauth-fetch.d.ts.map +1 -0
- package/dist/oauth-fetch.js +106 -0
- package/dist/oauth-fetch.js.map +1 -0
- package/dist/oauth.d.ts +120 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +202 -0
- package/dist/oauth.js.map +1 -0
- package/dist/oauth.test.d.ts +2 -0
- package/dist/oauth.test.d.ts.map +1 -0
- package/dist/oauth.test.js +211 -0
- package/dist/oauth.test.js.map +1 -0
- package/package.json +57 -0
|
@@ -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
|
package/dist/host.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|