@objectstack/connector-mcp 14.8.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.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +143 -0
- package/dist/index.d.mts +93 -15
- package/dist/index.d.ts +93 -15
- package/dist/index.js +125 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +122 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/connector-mcp-plugin.test.ts +31 -0
- package/src/connector-mcp-plugin.ts +69 -24
- package/src/index.ts +6 -0
- package/src/mcp-provider.test.ts +193 -0
- package/src/mcp-provider.ts +205 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/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
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
13
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m10.44 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m32.67 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 102ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.js [22m[32m12.20 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m34.21 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 103ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 16336ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m10.93 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m10.93 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,148 @@
|
|
|
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
|
+
|
|
134
|
+
## 15.0.0
|
|
135
|
+
|
|
136
|
+
### Patch Changes
|
|
137
|
+
|
|
138
|
+
- Updated dependencies [28b7c28]
|
|
139
|
+
- Updated dependencies [13749ec]
|
|
140
|
+
- Updated dependencies [e62c233]
|
|
141
|
+
- Updated dependencies [ed61c9b]
|
|
142
|
+
- Updated dependencies [31d04d4]
|
|
143
|
+
- @objectstack/spec@15.0.0
|
|
144
|
+
- @objectstack/core@15.0.0
|
|
145
|
+
|
|
3
146
|
## 14.8.0
|
|
4
147
|
|
|
5
148
|
### 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
|
|
116
|
-
* needs no runtime dependency on
|
|
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
|
-
|
|
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 —
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
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()`
|
|
131
|
-
* `
|
|
132
|
-
* — or the server is unreachable at boot — the
|
|
133
|
-
*
|
|
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
|
|
145
|
-
init(
|
|
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
|
|
116
|
-
* needs no runtime dependency on
|
|
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
|
-
|
|
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 —
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
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()`
|
|
131
|
-
* `
|
|
132
|
-
* — or the server is unreachable at boot — the
|
|
133
|
-
*
|
|
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
|
|
145
|
-
init(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
323
|
+
MCP_PROVIDER_KEY,
|
|
324
|
+
createMcpConnector,
|
|
325
|
+
createMcpProviderFactory
|
|
212
326
|
});
|
|
213
327
|
//# sourceMappingURL=index.js.map
|