@objectstack/connector-mcp 15.0.0 → 15.1.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @objectstack/connector-mcp@15.0.0 build /home/runner/work/framework/framework/packages/connectors/connector-mcp
2
+ > @objectstack/connector-mcp@15.1.0 build /home/runner/work/framework/framework/packages/connectors/connector-mcp
3
3
  > tsup --config ../../../tsup.config.ts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -10,13 +10,13 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- CJS dist/index.js 7.45 KB
14
- CJS dist/index.js.map 20.01 KB
15
- CJS ⚡️ Build success in 96ms
16
- ESM dist/index.mjs 5.81 KB
17
- ESM dist/index.mjs.map 18.62 KB
18
- ESM ⚡️ Build success in 96ms
13
+ ESM dist/index.mjs 10.44 KB
14
+ ESM dist/index.mjs.map 32.67 KB
15
+ ESM ⚡️ Build success in 102ms
16
+ CJS dist/index.js 12.20 KB
17
+ CJS dist/index.js.map 34.21 KB
18
+ CJS ⚡️ Build success in 103ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 15675ms
21
- DTS dist/index.d.mts 6.71 KB
22
- DTS dist/index.d.ts 6.71 KB
20
+ DTS ⚡️ Build success in 16336ms
21
+ DTS dist/index.d.mts 10.93 KB
22
+ DTS dist/index.d.ts 10.93 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,136 @@
1
1
  # @objectstack/connector-mcp
2
2
 
3
+ ## 15.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f531a26: feat(connectors): ADR-0096 — provider-bound declarative connector instances materialized at boot (#2977)
8
+
9
+ Declarative `connectors:` stack entries used to be **descriptor-only** (#2612):
10
+ registered as metadata but never dispatchable, the platform's one dead metadata
11
+ surface. An entry may now name a **`provider`** — an installed generic executor
12
+ (`openapi` / `mcp` / `rest`) — and the automation service **materializes** it
13
+ into a live, dispatchable connector at boot. AI can now wire an integration as
14
+ pure metadata and a flow `connector_action` calls it end-to-end.
15
+
16
+ - **Schema (`@objectstack/spec`).** `ConnectorSchema` gains `provider`,
17
+ `providerConfig`, and `auth` (a `credentialRef`-based instance-auth shape —
18
+ `ConnectorInstanceAuthSchema` — that references credentials, never inlines
19
+ them); `authentication` now defaults to `{ type: 'none' }` so a provider-bound
20
+ instance need not author it (loosening — existing connectors are unaffected).
21
+ `DeclarativeConnectorEntrySchema` (used by `stack.zod.ts`) rejects inline
22
+ secrets, orphan `providerConfig`/`auth`, and authored `actions`/`triggers` on a
23
+ provider-bound entry. A new `integration/connector-provider.ts` defines the
24
+ provider-factory contract as pure types.
25
+
26
+ - **Engine + boot (`@objectstack/service-automation`).** The engine adds a
27
+ connector-provider registry (`registerConnectorProvider`/`getConnectorProvider`)
28
+ and origin-tags registered connectors. At boot the service resolves each
29
+ provider-bound entry — looking up the factory, resolving `auth.credentialRef`
30
+ via a pluggable `CredentialResolver` (open-tier default: environment
31
+ variables), and registering the materialized connector. Boot **fails loudly**
32
+ for an unknown provider, invalid `providerConfig`, an unresolvable
33
+ `credentialRef`, or a name conflict with a plugin-registered connector (no
34
+ silent precedence).
35
+
36
+ - **Providers (`connector-rest` / `connector-openapi` / `connector-mcp`).** Each
37
+ plugin registers a provider factory in `init()` reusing its existing
38
+ generator/adapter API. Plugin options are now **optional**: with none the
39
+ plugin contributes only its provider factory; with instance options it also
40
+ registers a hand-wired connector (back-compat). `connector-openapi` adds a
41
+ `ConnectorOpenApiPlugin`.
42
+
43
+ Open tier: static auth (`none`/`api-key`/`basic`/`bearer`) with `credentialRef`
44
+ resolved from env vars. Managed vaulting, OAuth2 refresh, and per-tenant
45
+ connection lifecycle remain the enterprise tier (ADR-0015) — an enterprise host
46
+ injects a vault-backed `CredentialResolver` with no change to the materialization
47
+ path.
48
+
49
+ - f531a26: feat(connectors): degrade + retry declarative instances whose upstream is unreachable (#3017)
50
+
51
+ ADR-0097 kept every declarative-connector materialization failure fatal at
52
+ boot. That is right for configuration faults (unknown provider, invalid
53
+ `providerConfig`, unresolvable `credentialRef`, name conflict) but wrong for
54
+ _operational_ ones: a `provider: 'mcp'` instance must contact its MCP server
55
+ (`tools/list`) to materialize, and a transient network blip aborted the whole
56
+ app boot.
57
+
58
+ - **spec**: a provider factory can now throw
59
+ `ConnectorUpstreamUnavailableError` (code `CONNECTOR_UPSTREAM_UNAVAILABLE`,
60
+ structural guard `isConnectorUpstreamUnavailable`) to mark a failure as
61
+ "upstream temporarily unreachable — degrade and retry" instead of fatal.
62
+ - **service-automation**: the reconcile degrades such an instance in both boot
63
+ and reload modes: it registers an action-less husk (`state: 'degraded'` +
64
+ `degradedReason` on the `GET /connectors` descriptor) so the instance is
65
+ visible instead of silently missing — or, on a changed-config
66
+ re-materialization, keeps the old connector serving. A `connector_action`
67
+ against a degraded instance fails with the reason and a "retries
68
+ automatically" pointer. Degraded instances retry on an exponential backoff
69
+ (5s → 5min, reset by config edits) and on every `metadata:reloaded`
70
+ reconcile; recovery swaps the husk for the live connector atomically.
71
+ Reconcile runs (boot / reload / retry timer) are now serialized.
72
+ - **connector-mcp**: the `mcp` provider classifies connect / `tools/list`
73
+ failures as upstream-unavailable; transport-shape validation stays a plain
74
+ (fatal) throw.
75
+
76
+ Configuration faults remain loud boot failures — the carve-out is only for the
77
+ unavailable marker.
78
+
79
+ - f531a26: feat(connector-mcp)!: policy-gate declarative stdio transports — default-deny + host allowlist (#3055)
80
+
81
+ A declarative `provider: 'mcp'` entry with a stdio transport spawns a local
82
+ child process **from metadata** — which a runtime Studio publish can introduce.
83
+ Declarative stdio is now **denied by default**; hosts opt in deliberately:
84
+
85
+ ```ts
86
+ new ConnectorMcpPlugin({ declarativeStdio: ["my-mcp-server"] }); // command allowlist
87
+ new ConnectorMcpPlugin({ declarativeStdio: true }); // allow any (full trust)
88
+ ```
89
+
90
+ Behavior change: a declarative stdio instance that materialized before now
91
+ fails as a **configuration fault** (fatal at boot / skipped on reload) with an
92
+ actionable opt-in message, and is never classified upstream-unavailable — a
93
+ security rejection must not be retried into existence. `http` transports and
94
+ **hand-wired** connectors (plugin instance options / `createMcpConnector`) are
95
+ unaffected. This is the security precondition for shipping `connector-mcp` in
96
+ default presets (#3056); see ADR-0097 §"Declarative stdio policy" and
97
+ ADR-0024 §4.
98
+
99
+ ### Patch Changes
100
+
101
+ - Updated dependencies [f531a26]
102
+ - Updated dependencies [f531a26]
103
+ - Updated dependencies [f531a26]
104
+ - Updated dependencies [f531a26]
105
+ - Updated dependencies [f531a26]
106
+ - Updated dependencies [f531a26]
107
+ - Updated dependencies [3fe9df1]
108
+ - Updated dependencies [f531a26]
109
+ - Updated dependencies [f531a26]
110
+ - Updated dependencies [f531a26]
111
+ - Updated dependencies [f531a26]
112
+ - Updated dependencies [f531a26]
113
+ - Updated dependencies [f531a26]
114
+ - Updated dependencies [f531a26]
115
+ - Updated dependencies [f531a26]
116
+ - Updated dependencies [f531a26]
117
+ - Updated dependencies [f531a26]
118
+ - Updated dependencies [f531a26]
119
+ - Updated dependencies [f531a26]
120
+ - Updated dependencies [4109153]
121
+ - Updated dependencies [f531a26]
122
+ - Updated dependencies [f531a26]
123
+ - Updated dependencies [f531a26]
124
+ - Updated dependencies [f531a26]
125
+ - Updated dependencies [f531a26]
126
+ - Updated dependencies [f531a26]
127
+ - Updated dependencies [627f225]
128
+ - Updated dependencies [f531a26]
129
+ - Updated dependencies [f531a26]
130
+ - Updated dependencies [f531a26]
131
+ - @objectstack/spec@15.1.0
132
+ - @objectstack/core@15.1.0
133
+
3
134
  ## 15.0.0
4
135
 
5
136
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { Connector } from '@objectstack/spec/integration';
1
+ import { Connector, ConnectorProviderFactory } from '@objectstack/spec/integration';
2
2
  import { Plugin, PluginContext } from '@objectstack/core';
3
3
 
4
4
  /**
@@ -110,27 +110,104 @@ interface McpConnectorBundle {
110
110
  */
111
111
  declare function createMcpConnector(opts: McpConnectorOptions): Promise<McpConnectorBundle>;
112
112
 
113
+ /**
114
+ * The provider key this package contributes (ADR-0097). A declarative
115
+ * `connectors:` entry with `provider: 'mcp'` is materialized by this factory.
116
+ */
117
+ declare const MCP_PROVIDER_KEY = "mcp";
118
+ /**
119
+ * Host policy for **declarative** stdio transports (#3055). A stdio transport
120
+ * launches a local child process, and declarative entries arrive through
121
+ * metadata — including a runtime Studio publish — so spawning from them is
122
+ * gated OFF by default:
123
+ *
124
+ * - `undefined` / `false` — deny (default): a `provider: 'mcp'` entry with a
125
+ * stdio transport is rejected as a configuration fault.
126
+ * - `string[]` — allowlist: the transport's `command` must strictly equal one
127
+ * of the listed commands. NOTE this is a coarse trust boundary — listing a
128
+ * launcher like `npx` effectively allows any package it can run; list the
129
+ * specific server binaries you trust. Sandboxed execution is the enterprise
130
+ * tier (ADR-0024 §4).
131
+ * - `true` — allow any command (explicit full trust; hosts that treat every
132
+ * metadata author as an operator).
133
+ *
134
+ * Hand-wired connectors (plugin instance options / `createMcpConnector`) are
135
+ * NOT subject to this policy: their command was written in host code, a
136
+ * different trust anchor than metadata.
137
+ */
138
+ type McpDeclarativeStdioPolicy = boolean | string[];
139
+ /** Injectable dependencies for {@link createMcpProviderFactory} (tests). */
140
+ interface McpProviderDeps {
141
+ /** Injected MCP client factory; defaults to the SDK-backed client. */
142
+ clientFactory?: McpConnectorOptions['clientFactory'];
143
+ /** Policy for declarative stdio transports (#3055). Default: deny. */
144
+ declarativeStdio?: McpDeclarativeStdioPolicy;
145
+ }
146
+ /**
147
+ * Build the `mcp` {@link ConnectorProviderFactory} (ADR-0097 / ADR-0024). At boot
148
+ * the automation service invokes it for each `provider: 'mcp'` declarative
149
+ * instance: it connects to the MCP server named by `providerConfig.transport`,
150
+ * lists its tools, and produces the same `{ def, handlers, close }` bundle
151
+ * {@link createMcpConnector} builds for a hand-wired MCP connector — one action
152
+ * per tool, dispatched to the server's `tools/call`.
153
+ *
154
+ * Stdio transports on declarative instances are policy-gated (default deny) —
155
+ * see {@link McpDeclarativeStdioPolicy} (#3055).
156
+ *
157
+ * The connection is opened at materialization. Faults are classified (#3017):
158
+ * an invalid transport shape is a *configuration* fault and throws plain —
159
+ * fatal at boot per the ADR-0097 fail-loud contract — while a connect /
160
+ * `tools/list` failure (server down, refused, timed out) is an *operational*
161
+ * fault and throws {@link ConnectorUpstreamUnavailableError}, which the
162
+ * materializer turns into a degraded instance that is retried with backoff
163
+ * instead of aborting the whole app boot.
164
+ */
165
+ declare function createMcpProviderFactory(deps?: McpProviderDeps): ConnectorProviderFactory;
166
+
113
167
  /**
114
168
  * Minimal surface of the automation engine this plugin depends on — the
115
- * connector registry from ADR-0018 §Addendum. Kept structural so the plugin
116
- * needs no runtime dependency on `@objectstack/service-automation`.
169
+ * connector registry (ADR-0018 §Addendum) plus the provider registry (ADR-0097).
170
+ * Kept structural so the plugin needs no runtime dependency on
171
+ * `@objectstack/service-automation`.
117
172
  */
118
173
  interface ConnectorRegistrySurface {
119
174
  registerConnector(def: Connector, handlers: Record<string, (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>>): void;
120
175
  unregisterConnector(name: string): void;
176
+ registerConnectorProvider(providerKey: string, factory: ConnectorProviderFactory): void;
121
177
  }
122
- interface ConnectorMcpPluginOptions extends McpConnectorOptions {
178
+ /**
179
+ * Options for {@link ConnectorMcpPlugin}. All optional (ADR-0097): with no
180
+ * `transport` the plugin contributes only the `mcp` provider factory — so a
181
+ * stack can declare `provider: 'mcp'` instances as pure metadata. Supply a
182
+ * `transport` to ALSO connect one hand-wired MCP server at `start()`.
183
+ */
184
+ interface ConnectorMcpPluginOptions extends Partial<McpConnectorOptions> {
185
+ /**
186
+ * Policy for stdio transports on **declarative** `provider: 'mcp'`
187
+ * instances (#3055). Default **deny**: metadata (including a runtime Studio
188
+ * publish) must not spawn local processes unless the host opts in.
189
+ * `string[]` allowlists specific commands; `true` allows any. Hand-wired
190
+ * connectors configured via these plugin options are not subject to it —
191
+ * their command lives in host code, not metadata.
192
+ */
193
+ declarativeStdio?: McpDeclarativeStdioPolicy;
123
194
  }
124
195
  /**
125
- * ConnectorMcpPlugin — connects to an MCP server, discovers its tools, and
126
- * registers them as a single connector on the automation engine (ADR-0024).
127
- * One generic adapter, configured per server (transport + `include`), never
128
- * per-server code.
196
+ * ConnectorMcpPlugin — contributes the generic MCP adapter (ADR-0024) in two forms:
197
+ *
198
+ * 1. **Provider factory** (`mcp`, ADR-0097): registered at `init()` so the
199
+ * automation service can materialize declarative `provider: 'mcp'`
200
+ * `connectors:` entries — connecting to the server and mapping its tools to
201
+ * connector actions — at boot.
202
+ * 2. **Hand-wired instance** (optional, back-compat): when constructed with a
203
+ * `transport`, it also connects that one server at `start()` and registers
204
+ * the resulting connector.
129
205
  *
130
- * Lifecycle: on `start()` it connects and builds the connector once; on
131
- * `stop()` it tears the MCP connection down. If no automation engine is present
132
- * — or the server is unreachable at boot — the plugin logs and skips: a missing
133
- * optional connector is not a fatal error (same posture as `ConnectorRestPlugin`).
206
+ * Lifecycle: on `start()` a configured instance connects and builds the
207
+ * connector once; on `destroy()` it tears the MCP connection down. If no
208
+ * automation engine is present — or the server is unreachable at boot — the
209
+ * hand-wired path logs and skips: a missing optional connector is not fatal
210
+ * (unlike a *declarative* provider-bound instance, which fails boot loudly).
134
211
  */
135
212
  declare class ConnectorMcpPlugin implements Plugin {
136
213
  name: string;
@@ -141,8 +218,8 @@ declare class ConnectorMcpPlugin implements Plugin {
141
218
  private connectorName?;
142
219
  private automation?;
143
220
  private close?;
144
- constructor(options: ConnectorMcpPluginOptions);
145
- init(_ctx: PluginContext): Promise<void>;
221
+ constructor(options?: ConnectorMcpPluginOptions);
222
+ init(ctx: PluginContext): Promise<void>;
146
223
  start(ctx: PluginContext): Promise<void>;
147
224
  /**
148
225
  * Destroy phase — the kernel's shutdown hook (the `Plugin` lifecycle exposes
@@ -150,6 +227,7 @@ declare class ConnectorMcpPlugin implements Plugin {
150
227
  * connection down so no child process / socket is leaked.
151
228
  */
152
229
  destroy(): Promise<void>;
230
+ private tryGetAutomation;
153
231
  }
154
232
 
155
- export { ConnectorMcpPlugin, type ConnectorMcpPluginOptions, type ConnectorRegistrySurface, type McpClientLike, type McpConnectorBundle, type McpConnectorOptions, type McpToolDescriptor, type McpTransport, createMcpConnector };
233
+ export { ConnectorMcpPlugin, type ConnectorMcpPluginOptions, type ConnectorRegistrySurface, MCP_PROVIDER_KEY, type McpClientLike, type McpConnectorBundle, type McpConnectorOptions, type McpDeclarativeStdioPolicy, type McpProviderDeps, type McpToolDescriptor, type McpTransport, createMcpConnector, createMcpProviderFactory };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Connector } from '@objectstack/spec/integration';
1
+ import { Connector, ConnectorProviderFactory } from '@objectstack/spec/integration';
2
2
  import { Plugin, PluginContext } from '@objectstack/core';
3
3
 
4
4
  /**
@@ -110,27 +110,104 @@ interface McpConnectorBundle {
110
110
  */
111
111
  declare function createMcpConnector(opts: McpConnectorOptions): Promise<McpConnectorBundle>;
112
112
 
113
+ /**
114
+ * The provider key this package contributes (ADR-0097). A declarative
115
+ * `connectors:` entry with `provider: 'mcp'` is materialized by this factory.
116
+ */
117
+ declare const MCP_PROVIDER_KEY = "mcp";
118
+ /**
119
+ * Host policy for **declarative** stdio transports (#3055). A stdio transport
120
+ * launches a local child process, and declarative entries arrive through
121
+ * metadata — including a runtime Studio publish — so spawning from them is
122
+ * gated OFF by default:
123
+ *
124
+ * - `undefined` / `false` — deny (default): a `provider: 'mcp'` entry with a
125
+ * stdio transport is rejected as a configuration fault.
126
+ * - `string[]` — allowlist: the transport's `command` must strictly equal one
127
+ * of the listed commands. NOTE this is a coarse trust boundary — listing a
128
+ * launcher like `npx` effectively allows any package it can run; list the
129
+ * specific server binaries you trust. Sandboxed execution is the enterprise
130
+ * tier (ADR-0024 §4).
131
+ * - `true` — allow any command (explicit full trust; hosts that treat every
132
+ * metadata author as an operator).
133
+ *
134
+ * Hand-wired connectors (plugin instance options / `createMcpConnector`) are
135
+ * NOT subject to this policy: their command was written in host code, a
136
+ * different trust anchor than metadata.
137
+ */
138
+ type McpDeclarativeStdioPolicy = boolean | string[];
139
+ /** Injectable dependencies for {@link createMcpProviderFactory} (tests). */
140
+ interface McpProviderDeps {
141
+ /** Injected MCP client factory; defaults to the SDK-backed client. */
142
+ clientFactory?: McpConnectorOptions['clientFactory'];
143
+ /** Policy for declarative stdio transports (#3055). Default: deny. */
144
+ declarativeStdio?: McpDeclarativeStdioPolicy;
145
+ }
146
+ /**
147
+ * Build the `mcp` {@link ConnectorProviderFactory} (ADR-0097 / ADR-0024). At boot
148
+ * the automation service invokes it for each `provider: 'mcp'` declarative
149
+ * instance: it connects to the MCP server named by `providerConfig.transport`,
150
+ * lists its tools, and produces the same `{ def, handlers, close }` bundle
151
+ * {@link createMcpConnector} builds for a hand-wired MCP connector — one action
152
+ * per tool, dispatched to the server's `tools/call`.
153
+ *
154
+ * Stdio transports on declarative instances are policy-gated (default deny) —
155
+ * see {@link McpDeclarativeStdioPolicy} (#3055).
156
+ *
157
+ * The connection is opened at materialization. Faults are classified (#3017):
158
+ * an invalid transport shape is a *configuration* fault and throws plain —
159
+ * fatal at boot per the ADR-0097 fail-loud contract — while a connect /
160
+ * `tools/list` failure (server down, refused, timed out) is an *operational*
161
+ * fault and throws {@link ConnectorUpstreamUnavailableError}, which the
162
+ * materializer turns into a degraded instance that is retried with backoff
163
+ * instead of aborting the whole app boot.
164
+ */
165
+ declare function createMcpProviderFactory(deps?: McpProviderDeps): ConnectorProviderFactory;
166
+
113
167
  /**
114
168
  * Minimal surface of the automation engine this plugin depends on — the
115
- * connector registry from ADR-0018 §Addendum. Kept structural so the plugin
116
- * needs no runtime dependency on `@objectstack/service-automation`.
169
+ * connector registry (ADR-0018 §Addendum) plus the provider registry (ADR-0097).
170
+ * Kept structural so the plugin needs no runtime dependency on
171
+ * `@objectstack/service-automation`.
117
172
  */
118
173
  interface ConnectorRegistrySurface {
119
174
  registerConnector(def: Connector, handlers: Record<string, (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>>): void;
120
175
  unregisterConnector(name: string): void;
176
+ registerConnectorProvider(providerKey: string, factory: ConnectorProviderFactory): void;
121
177
  }
122
- interface ConnectorMcpPluginOptions extends McpConnectorOptions {
178
+ /**
179
+ * Options for {@link ConnectorMcpPlugin}. All optional (ADR-0097): with no
180
+ * `transport` the plugin contributes only the `mcp` provider factory — so a
181
+ * stack can declare `provider: 'mcp'` instances as pure metadata. Supply a
182
+ * `transport` to ALSO connect one hand-wired MCP server at `start()`.
183
+ */
184
+ interface ConnectorMcpPluginOptions extends Partial<McpConnectorOptions> {
185
+ /**
186
+ * Policy for stdio transports on **declarative** `provider: 'mcp'`
187
+ * instances (#3055). Default **deny**: metadata (including a runtime Studio
188
+ * publish) must not spawn local processes unless the host opts in.
189
+ * `string[]` allowlists specific commands; `true` allows any. Hand-wired
190
+ * connectors configured via these plugin options are not subject to it —
191
+ * their command lives in host code, not metadata.
192
+ */
193
+ declarativeStdio?: McpDeclarativeStdioPolicy;
123
194
  }
124
195
  /**
125
- * ConnectorMcpPlugin — connects to an MCP server, discovers its tools, and
126
- * registers them as a single connector on the automation engine (ADR-0024).
127
- * One generic adapter, configured per server (transport + `include`), never
128
- * per-server code.
196
+ * ConnectorMcpPlugin — contributes the generic MCP adapter (ADR-0024) in two forms:
197
+ *
198
+ * 1. **Provider factory** (`mcp`, ADR-0097): registered at `init()` so the
199
+ * automation service can materialize declarative `provider: 'mcp'`
200
+ * `connectors:` entries — connecting to the server and mapping its tools to
201
+ * connector actions — at boot.
202
+ * 2. **Hand-wired instance** (optional, back-compat): when constructed with a
203
+ * `transport`, it also connects that one server at `start()` and registers
204
+ * the resulting connector.
129
205
  *
130
- * Lifecycle: on `start()` it connects and builds the connector once; on
131
- * `stop()` it tears the MCP connection down. If no automation engine is present
132
- * — or the server is unreachable at boot — the plugin logs and skips: a missing
133
- * optional connector is not a fatal error (same posture as `ConnectorRestPlugin`).
206
+ * Lifecycle: on `start()` a configured instance connects and builds the
207
+ * connector once; on `destroy()` it tears the MCP connection down. If no
208
+ * automation engine is present — or the server is unreachable at boot — the
209
+ * hand-wired path logs and skips: a missing optional connector is not fatal
210
+ * (unlike a *declarative* provider-bound instance, which fails boot loudly).
134
211
  */
135
212
  declare class ConnectorMcpPlugin implements Plugin {
136
213
  name: string;
@@ -141,8 +218,8 @@ declare class ConnectorMcpPlugin implements Plugin {
141
218
  private connectorName?;
142
219
  private automation?;
143
220
  private close?;
144
- constructor(options: ConnectorMcpPluginOptions);
145
- init(_ctx: PluginContext): Promise<void>;
221
+ constructor(options?: ConnectorMcpPluginOptions);
222
+ init(ctx: PluginContext): Promise<void>;
146
223
  start(ctx: PluginContext): Promise<void>;
147
224
  /**
148
225
  * Destroy phase — the kernel's shutdown hook (the `Plugin` lifecycle exposes
@@ -150,6 +227,7 @@ declare class ConnectorMcpPlugin implements Plugin {
150
227
  * connection down so no child process / socket is leaked.
151
228
  */
152
229
  destroy(): Promise<void>;
230
+ private tryGetAutomation;
153
231
  }
154
232
 
155
- export { ConnectorMcpPlugin, type ConnectorMcpPluginOptions, type ConnectorRegistrySurface, type McpClientLike, type McpConnectorBundle, type McpConnectorOptions, type McpToolDescriptor, type McpTransport, createMcpConnector };
233
+ export { ConnectorMcpPlugin, type ConnectorMcpPluginOptions, type ConnectorRegistrySurface, MCP_PROVIDER_KEY, type McpClientLike, type McpConnectorBundle, type McpConnectorOptions, type McpDeclarativeStdioPolicy, type McpProviderDeps, type McpToolDescriptor, type McpTransport, createMcpConnector, createMcpProviderFactory };
package/dist/index.js CHANGED
@@ -31,7 +31,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ConnectorMcpPlugin: () => ConnectorMcpPlugin,
34
- createMcpConnector: () => createMcpConnector
34
+ MCP_PROVIDER_KEY: () => MCP_PROVIDER_KEY,
35
+ createMcpConnector: () => createMcpConnector,
36
+ createMcpProviderFactory: () => createMcpProviderFactory
35
37
  });
36
38
  module.exports = __toCommonJS(index_exports);
37
39
 
@@ -145,25 +147,128 @@ async function createMcpConnector(opts) {
145
147
  };
146
148
  }
147
149
 
150
+ // src/mcp-provider.ts
151
+ var import_integration = require("@objectstack/spec/integration");
152
+ var MCP_PROVIDER_KEY = "mcp";
153
+ function isStringRecord(v) {
154
+ if (!v || typeof v !== "object" || Array.isArray(v)) return false;
155
+ return Object.values(v).every((x) => typeof x === "string");
156
+ }
157
+ function applyAuthToHeaders(auth, headers) {
158
+ if (!auth || auth.type === "none") return;
159
+ switch (auth.type) {
160
+ case "bearer":
161
+ headers["Authorization"] = `Bearer ${auth.token}`;
162
+ return;
163
+ case "basic":
164
+ headers["Authorization"] = `Basic ${Buffer.from(`${auth.username}:${auth.password}`).toString("base64")}`;
165
+ return;
166
+ case "api-key":
167
+ if (!auth.paramName) headers[auth.headerName ?? "X-API-Key"] = auth.key;
168
+ return;
169
+ }
170
+ }
171
+ function normalizeTransport(raw, connectorName, auth) {
172
+ if (!raw || typeof raw !== "object") {
173
+ throw new Error(
174
+ `connector-mcp provider: connector '${connectorName}' requires providerConfig.transport ({ kind: 'stdio', command, ... } or { kind: 'http', url, ... }).`
175
+ );
176
+ }
177
+ const t = raw;
178
+ if (t.kind === "stdio") {
179
+ if (typeof t.command !== "string" || t.command.length === 0) {
180
+ throw new Error(
181
+ `connector-mcp provider: connector '${connectorName}' stdio transport requires a 'command' string.`
182
+ );
183
+ }
184
+ return {
185
+ kind: "stdio",
186
+ command: t.command,
187
+ args: Array.isArray(t.args) ? t.args.map((a) => String(a)) : void 0,
188
+ env: isStringRecord(t.env) ? t.env : void 0
189
+ };
190
+ }
191
+ if (t.kind === "http") {
192
+ if (typeof t.url !== "string" || t.url.length === 0) {
193
+ throw new Error(
194
+ `connector-mcp provider: connector '${connectorName}' http transport requires a 'url' string.`
195
+ );
196
+ }
197
+ const headers = { ...isStringRecord(t.headers) ? t.headers : {} };
198
+ applyAuthToHeaders(auth, headers);
199
+ return { kind: "http", url: t.url, headers: Object.keys(headers).length > 0 ? headers : void 0 };
200
+ }
201
+ throw new Error(
202
+ `connector-mcp provider: connector '${connectorName}' providerConfig.transport.kind must be 'stdio' or 'http'.`
203
+ );
204
+ }
205
+ function assertDeclarativeStdioAllowed(policy, command, connectorName) {
206
+ if (policy === true) return;
207
+ if (Array.isArray(policy)) {
208
+ if (policy.includes(command)) return;
209
+ throw new Error(
210
+ `connector-mcp provider: connector '${connectorName}' declares a stdio transport with command '${command}', which is not in the host's declarativeStdio allowlist [${policy.join(", ")}]. Add the command to new ConnectorMcpPlugin({ declarativeStdio: [...] }) if this server is trusted (#3055).`
211
+ );
212
+ }
213
+ throw new Error(
214
+ `connector-mcp provider: connector '${connectorName}' declares a stdio transport (command '${command}'), but declarative stdio transports are disabled by default \u2014 a stdio transport launches a local process from stack metadata (including runtime Studio publishes). If this server is trusted, opt in deliberately: new ConnectorMcpPlugin({ declarativeStdio: ['${command}'] }) \u2014 or use an http transport (#3055, ADR-0024 \xA74).`
215
+ );
216
+ }
217
+ function createMcpProviderFactory(deps = {}) {
218
+ return async (ctx) => {
219
+ const cfg = ctx.providerConfig ?? {};
220
+ const transport = normalizeTransport(cfg.transport, ctx.name, ctx.auth);
221
+ if (transport.kind === "stdio") {
222
+ assertDeclarativeStdioAllowed(deps.declarativeStdio, transport.command, ctx.name);
223
+ }
224
+ const includeList = Array.isArray(cfg.include) ? cfg.include.filter((x) => typeof x === "string") : void 0;
225
+ const include = includeList ? (toolName) => includeList.includes(toolName) : void 0;
226
+ let bundle;
227
+ try {
228
+ bundle = await createMcpConnector({
229
+ name: ctx.name,
230
+ label: ctx.label,
231
+ description: ctx.description,
232
+ transport,
233
+ include,
234
+ clientFactory: deps.clientFactory
235
+ });
236
+ } catch (err) {
237
+ throw new import_integration.ConnectorUpstreamUnavailableError(
238
+ `connector-mcp provider: connector '${ctx.name}' could not reach its MCP server: ${err.message}`,
239
+ { cause: err }
240
+ );
241
+ }
242
+ return { def: bundle.def, handlers: bundle.handlers, close: bundle.close };
243
+ };
244
+ }
245
+
148
246
  // src/connector-mcp-plugin.ts
149
247
  var ConnectorMcpPlugin = class {
150
- constructor(options) {
248
+ constructor(options = {}) {
151
249
  this.name = "com.objectstack.connector.mcp";
152
250
  this.version = "1.0.0";
153
251
  this.type = "standard";
154
- // Ensure the automation engine (and its connector registry) is started first.
252
+ // Ensure the automation engine (and its connector/provider registries) exist first.
155
253
  this.dependencies = ["com.objectstack.service-automation"];
156
254
  this.options = options;
157
255
  }
158
- async init(_ctx) {
256
+ async init(ctx) {
257
+ const automation = this.tryGetAutomation(ctx);
258
+ if (automation && typeof automation.registerConnectorProvider === "function") {
259
+ automation.registerConnectorProvider(
260
+ MCP_PROVIDER_KEY,
261
+ createMcpProviderFactory({
262
+ clientFactory: this.options.clientFactory,
263
+ declarativeStdio: this.options.declarativeStdio
264
+ })
265
+ );
266
+ ctx.logger.info("ConnectorMcpPlugin: registered 'mcp' connector provider");
267
+ }
159
268
  }
160
269
  async start(ctx) {
161
- let automation;
162
- try {
163
- automation = ctx.getService("automation");
164
- } catch {
165
- automation = void 0;
166
- }
270
+ if (!this.options.transport) return;
271
+ const automation = this.tryGetAutomation(ctx);
167
272
  if (!automation || typeof automation.registerConnector !== "function") {
168
273
  ctx.logger.info("ConnectorMcpPlugin: no automation engine \u2014 MCP connector not registered");
169
274
  return;
@@ -204,10 +309,19 @@ var ConnectorMcpPlugin = class {
204
309
  }
205
310
  }
206
311
  }
312
+ tryGetAutomation(ctx) {
313
+ try {
314
+ return ctx.getService("automation");
315
+ } catch {
316
+ return void 0;
317
+ }
318
+ }
207
319
  };
208
320
  // Annotate the CommonJS export names for ESM import in node:
209
321
  0 && (module.exports = {
210
322
  ConnectorMcpPlugin,
211
- createMcpConnector
323
+ MCP_PROVIDER_KEY,
324
+ createMcpConnector,
325
+ createMcpProviderFactory
212
326
  });
213
327
  //# sourceMappingURL=index.js.map