@objectstack/connector-mcp 17.0.0-rc.0 → 17.0.0-rc.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.
@@ -1,205 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import type { ConnectorProviderFactory, ResolvedConnectorAuth } from '@objectstack/spec/integration';
4
- import { ConnectorUpstreamUnavailableError } from '@objectstack/spec/integration';
5
- import { createMcpConnector, type McpConnectorOptions, type McpTransport } from './mcp-connector.js';
6
-
7
- /**
8
- * The provider key this package contributes (ADR-0097). A declarative
9
- * `connectors:` entry with `provider: 'mcp'` is materialized by this factory.
10
- */
11
- export const MCP_PROVIDER_KEY = 'mcp';
12
-
13
- /**
14
- * Host policy for **declarative** stdio transports (#3055). A stdio transport
15
- * launches a local child process, and declarative entries arrive through
16
- * metadata — including a runtime Studio publish — so spawning from them is
17
- * gated OFF by default:
18
- *
19
- * - `undefined` / `false` — deny (default): a `provider: 'mcp'` entry with a
20
- * stdio transport is rejected as a configuration fault.
21
- * - `string[]` — allowlist: the transport's `command` must strictly equal one
22
- * of the listed commands. NOTE this is a coarse trust boundary — listing a
23
- * launcher like `npx` effectively allows any package it can run; list the
24
- * specific server binaries you trust. Sandboxed execution is the enterprise
25
- * tier (ADR-0024 §4).
26
- * - `true` — allow any command (explicit full trust; hosts that treat every
27
- * metadata author as an operator).
28
- *
29
- * Hand-wired connectors (plugin instance options / `createMcpConnector`) are
30
- * NOT subject to this policy: their command was written in host code, a
31
- * different trust anchor than metadata.
32
- */
33
- export type McpDeclarativeStdioPolicy = boolean | string[];
34
-
35
- /** Injectable dependencies for {@link createMcpProviderFactory} (tests). */
36
- export interface McpProviderDeps {
37
- /** Injected MCP client factory; defaults to the SDK-backed client. */
38
- clientFactory?: McpConnectorOptions['clientFactory'];
39
- /** Policy for declarative stdio transports (#3055). Default: deny. */
40
- declarativeStdio?: McpDeclarativeStdioPolicy;
41
- }
42
-
43
- /** Shape of `providerConfig` for a `provider: 'mcp'` declarative instance. */
44
- interface McpProviderConfig {
45
- /** How to reach the MCP server (stdio or streamable-http). */
46
- transport?: unknown;
47
- /** Optional tool-name allowlist — only these tools become actions. */
48
- include?: unknown;
49
- }
50
-
51
- function isStringRecord(v: unknown): v is Record<string, string> {
52
- if (!v || typeof v !== 'object' || Array.isArray(v)) return false;
53
- return Object.values(v as Record<string, unknown>).every((x) => typeof x === 'string');
54
- }
55
-
56
- /**
57
- * Fold the resolved instance `auth` into an MCP **http** transport's headers
58
- * (ADR-0024 keeps MCP credentials with the transport). `credentialRef` has
59
- * already been resolved upstream, so this only maps the static credential to the
60
- * right header. Not applied to stdio transports — a stdio server receives its
61
- * credentials through `transport.env`.
62
- */
63
- function applyAuthToHeaders(
64
- auth: ResolvedConnectorAuth | undefined,
65
- headers: Record<string, string>,
66
- ): void {
67
- if (!auth || auth.type === 'none') return;
68
- switch (auth.type) {
69
- case 'bearer':
70
- headers['Authorization'] = `Bearer ${auth.token}`;
71
- return;
72
- case 'basic':
73
- headers['Authorization'] = `Basic ${Buffer.from(`${auth.username}:${auth.password}`).toString('base64')}`;
74
- return;
75
- case 'api-key':
76
- // Header-based only for MCP http (query-param keys are not part of the transport).
77
- if (!auth.paramName) headers[auth.headerName ?? 'X-API-Key'] = auth.key;
78
- return;
79
- }
80
- }
81
-
82
- /** Validate + normalize `providerConfig.transport`, injecting resolved auth for http. */
83
- function normalizeTransport(
84
- raw: unknown,
85
- connectorName: string,
86
- auth: ResolvedConnectorAuth | undefined,
87
- ): McpTransport {
88
- if (!raw || typeof raw !== 'object') {
89
- throw new Error(
90
- `connector-mcp provider: connector '${connectorName}' requires providerConfig.transport ` +
91
- `({ kind: 'stdio', command, ... } or { kind: 'http', url, ... }).`,
92
- );
93
- }
94
- const t = raw as Record<string, unknown>;
95
- if (t.kind === 'stdio') {
96
- if (typeof t.command !== 'string' || t.command.length === 0) {
97
- throw new Error(
98
- `connector-mcp provider: connector '${connectorName}' stdio transport requires a 'command' string.`,
99
- );
100
- }
101
- return {
102
- kind: 'stdio',
103
- command: t.command,
104
- args: Array.isArray(t.args) ? t.args.map((a) => String(a)) : undefined,
105
- env: isStringRecord(t.env) ? t.env : undefined,
106
- };
107
- }
108
- if (t.kind === 'http') {
109
- if (typeof t.url !== 'string' || t.url.length === 0) {
110
- throw new Error(
111
- `connector-mcp provider: connector '${connectorName}' http transport requires a 'url' string.`,
112
- );
113
- }
114
- const headers: Record<string, string> = { ...(isStringRecord(t.headers) ? t.headers : {}) };
115
- applyAuthToHeaders(auth, headers);
116
- return { kind: 'http', url: t.url, headers: Object.keys(headers).length > 0 ? headers : undefined };
117
- }
118
- throw new Error(
119
- `connector-mcp provider: connector '${connectorName}' providerConfig.transport.kind must be 'stdio' or 'http'.`,
120
- );
121
- }
122
-
123
- /**
124
- * Enforce the {@link McpDeclarativeStdioPolicy} for one declarative instance
125
- * (#3055). Throws a **plain** Error on violation: a security-policy rejection
126
- * is a configuration fault — fatal at boot, skipped+logged on reload — and must
127
- * never be classified upstream-unavailable (it cannot be retried into
128
- * existence).
129
- */
130
- function assertDeclarativeStdioAllowed(
131
- policy: McpDeclarativeStdioPolicy | undefined,
132
- command: string,
133
- connectorName: string,
134
- ): void {
135
- if (policy === true) return;
136
- if (Array.isArray(policy)) {
137
- if (policy.includes(command)) return;
138
- throw new Error(
139
- `connector-mcp provider: connector '${connectorName}' declares a stdio transport with command '${command}', ` +
140
- `which is not in the host's declarativeStdio allowlist [${policy.join(', ')}]. ` +
141
- `Add the command to new ConnectorMcpPlugin({ declarativeStdio: [...] }) if this server is trusted (#3055).`,
142
- );
143
- }
144
- throw new Error(
145
- `connector-mcp provider: connector '${connectorName}' declares a stdio transport (command '${command}'), ` +
146
- `but declarative stdio transports are disabled by default — a stdio transport launches a local process ` +
147
- `from stack metadata (including runtime Studio publishes). If this server is trusted, opt in deliberately: ` +
148
- `new ConnectorMcpPlugin({ declarativeStdio: ['${command}'] }) — or use an http transport (#3055, ADR-0024 §4).`,
149
- );
150
- }
151
-
152
- /**
153
- * Build the `mcp` {@link ConnectorProviderFactory} (ADR-0097 / ADR-0024). At boot
154
- * the automation service invokes it for each `provider: 'mcp'` declarative
155
- * instance: it connects to the MCP server named by `providerConfig.transport`,
156
- * lists its tools, and produces the same `{ def, handlers, close }` bundle
157
- * {@link createMcpConnector} builds for a hand-wired MCP connector — one action
158
- * per tool, dispatched to the server's `tools/call`.
159
- *
160
- * Stdio transports on declarative instances are policy-gated (default deny) —
161
- * see {@link McpDeclarativeStdioPolicy} (#3055).
162
- *
163
- * The connection is opened at materialization. Faults are classified (#3017):
164
- * an invalid transport shape is a *configuration* fault and throws plain —
165
- * fatal at boot per the ADR-0097 fail-loud contract — while a connect /
166
- * `tools/list` failure (server down, refused, timed out) is an *operational*
167
- * fault and throws {@link ConnectorUpstreamUnavailableError}, which the
168
- * materializer turns into a degraded instance that is retried with backoff
169
- * instead of aborting the whole app boot.
170
- */
171
- export function createMcpProviderFactory(deps: McpProviderDeps = {}): ConnectorProviderFactory {
172
- return async (ctx) => {
173
- const cfg = (ctx.providerConfig ?? {}) as McpProviderConfig;
174
- const transport = normalizeTransport(cfg.transport, ctx.name, ctx.auth);
175
- if (transport.kind === 'stdio') {
176
- assertDeclarativeStdioAllowed(deps.declarativeStdio, transport.command, ctx.name);
177
- }
178
- const includeList = Array.isArray(cfg.include)
179
- ? cfg.include.filter((x): x is string => typeof x === 'string')
180
- : undefined;
181
- const include = includeList ? (toolName: string) => includeList.includes(toolName) : undefined;
182
-
183
- let bundle;
184
- try {
185
- bundle = await createMcpConnector({
186
- name: ctx.name,
187
- label: ctx.label,
188
- description: ctx.description,
189
- transport,
190
- include,
191
- clientFactory: deps.clientFactory,
192
- });
193
- } catch (err) {
194
- // Everything past transport validation is talking to the server (connect,
195
- // handshake, tools/list) — operational, hence retryable. A credential the
196
- // server rejects also lands here: indistinguishable from the outside, and
197
- // retrying it is loud (logged per attempt), never silent.
198
- throw new ConnectorUpstreamUnavailableError(
199
- `connector-mcp provider: connector '${ctx.name}' could not reach its MCP server: ${(err as Error).message}`,
200
- { cause: err },
201
- );
202
- }
203
- return { def: bundle.def, handlers: bundle.handlers, close: bundle.close };
204
- };
205
- }
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src",
6
- "types": ["node"]
7
- },
8
- "include": ["src/**/*"],
9
- "exclude": ["dist", "node_modules", "**/*.test.ts"]
10
- }