@objectstack/connector-mcp 7.4.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 +22 -0
- package/CHANGELOG.md +44 -0
- package/LICENSE +93 -0
- package/dist/index.d.mts +155 -0
- package/dist/index.d.ts +155 -0
- package/dist/index.js +212 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +174 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +39 -0
- package/src/connector-mcp-plugin.test.ts +96 -0
- package/src/connector-mcp-plugin.ts +104 -0
- package/src/index.ts +33 -0
- package/src/mcp-connector.test.ts +194 -0
- package/src/mcp-connector.ts +265 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
> @objectstack/connector-mcp@7.4.0 build /home/runner/work/framework/framework/packages/connectors/connector-mcp
|
|
3
|
+
> tsup --config ../../../tsup.config.ts
|
|
4
|
+
|
|
5
|
+
[34mCLI[39m Building entry: src/index.ts
|
|
6
|
+
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
+
[34mCLI[39m tsup v8.5.1
|
|
8
|
+
[34mCLI[39m Using tsup config: /home/runner/work/framework/framework/tsup.config.ts
|
|
9
|
+
[34mCLI[39m Target: es2020
|
|
10
|
+
[34mCLI[39m Cleaning output folder
|
|
11
|
+
[34mESM[39m Build start
|
|
12
|
+
[34mCJS[39m Build start
|
|
13
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m5.69 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m18.22 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 101ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.js [22m[32m7.33 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m19.61 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 103ms
|
|
19
|
+
[34mDTS[39m Build start
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 14765ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m6.71 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m6.71 KB[39m
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @objectstack/connector-mcp
|
|
2
|
+
|
|
3
|
+
## 7.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6e4b3f2: MCP Connector (ADR-0024) — adopt any Model Context Protocol server as a connector.
|
|
8
|
+
|
|
9
|
+
Adds `@objectstack/connector-mcp`, a single generic adapter that turns _any_
|
|
10
|
+
MCP server into an ordinary `type: 'api'` connector on the automation engine —
|
|
11
|
+
no per-server code:
|
|
12
|
+
|
|
13
|
+
- `createMcpConnector({ transport, include?, … })` connects over **stdio** or
|
|
14
|
+
**streamable-HTTP**, calls `tools/list`, and maps each tool to a connector
|
|
15
|
+
action (`name → key`, `description → label/description`,
|
|
16
|
+
`inputSchema → inputSchema`). Handlers dispatch to `tools/call` and normalise
|
|
17
|
+
the result to the shared `{ ok, content, … }` envelope (logical tool errors
|
|
18
|
+
surface as `ok: false` rather than throwing).
|
|
19
|
+
- `ConnectorMcpPlugin` registers the connector via the existing
|
|
20
|
+
`engine.registerConnector()` path (no new engine surface, no `mcp_call` node)
|
|
21
|
+
and tears the MCP connection down on shutdown. Fail-soft: an unreachable
|
|
22
|
+
server or missing automation engine is logged and skipped.
|
|
23
|
+
- Credentials live with the MCP server (transport `env`/`headers`), never in
|
|
24
|
+
`ConnectorSchema` and never in the serialized, discovery-exposed `def`.
|
|
25
|
+
|
|
26
|
+
Open-tier scope: client adapter + operator-supplied static credentials. A
|
|
27
|
+
curated server registry, managed secrets, per-tenant lifecycle, and sandboxed
|
|
28
|
+
stdio execution remain the enterprise tier.
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- Updated dependencies [23c7107]
|
|
33
|
+
- Updated dependencies [c72daad]
|
|
34
|
+
- Updated dependencies [f115182]
|
|
35
|
+
- Updated dependencies [2faf9f2]
|
|
36
|
+
- Updated dependencies [2faf9f2]
|
|
37
|
+
- Updated dependencies [2faf9f2]
|
|
38
|
+
- Updated dependencies [58b450b]
|
|
39
|
+
- Updated dependencies [82eb6cf]
|
|
40
|
+
- Updated dependencies [13d8653]
|
|
41
|
+
- Updated dependencies [ff3d006]
|
|
42
|
+
- Updated dependencies [5e831de]
|
|
43
|
+
- @objectstack/spec@7.4.0
|
|
44
|
+
- @objectstack/core@7.4.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
|
|
2
|
+
"Business Source License" is a trademark of MariaDB Corporation Ab.
|
|
3
|
+
|
|
4
|
+
Parameters
|
|
5
|
+
|
|
6
|
+
Licensor: ObjectStack AI LLC
|
|
7
|
+
Licensed Work: ObjectStack Runtime: the BSL-licensed packages
|
|
8
|
+
of the ObjectStack monorepo as listed in LICENSING.md.
|
|
9
|
+
Copyright (c) 2026 ObjectStack AI LLC.
|
|
10
|
+
Additional Use Grant: You may make production use of the Licensed Work, provided
|
|
11
|
+
Your use does not include offering the Licensed Work to third
|
|
12
|
+
parties on a hosted or embedded basis in order to compete with
|
|
13
|
+
ObjectStack AI LLC's paid version(s) of the Licensed Work. For purposes
|
|
14
|
+
of this license:
|
|
15
|
+
|
|
16
|
+
A "competitive offering" is a Product that is offered to third
|
|
17
|
+
parties on a paid basis, including through paid support
|
|
18
|
+
arrangements, that significantly overlaps with the capabilities
|
|
19
|
+
of ObjectStack AI LLC's paid version(s) of the Licensed Work. If Your
|
|
20
|
+
Product is not a competitive offering when You first make it
|
|
21
|
+
generally available, it will not become a competitive offering
|
|
22
|
+
later due to ObjectStack AI LLC releasing a new version of the Licensed
|
|
23
|
+
Work with additional capabilities. In addition, Products that
|
|
24
|
+
are not provided on a paid basis are not competitive.
|
|
25
|
+
|
|
26
|
+
"Product" means software that is offered to end users to manage
|
|
27
|
+
in their own environments or offered as a service on a hosted
|
|
28
|
+
basis.
|
|
29
|
+
|
|
30
|
+
"Embedded" means including the source code or executable code
|
|
31
|
+
from the Licensed Work in a competitive offering. "Embedded"
|
|
32
|
+
also means packaging the competitive offering in such a way
|
|
33
|
+
that the Licensed Work must be accessed or downloaded for the
|
|
34
|
+
competitive offering to operate.
|
|
35
|
+
|
|
36
|
+
Hosting or using the Licensed Work(s) for internal purposes
|
|
37
|
+
within an organization is not considered a competitive
|
|
38
|
+
offering. ObjectStack AI LLC considers your organization to include all
|
|
39
|
+
of your affiliates under common control.
|
|
40
|
+
|
|
41
|
+
For binding interpretive guidance on using ObjectStack AI LLC products
|
|
42
|
+
under the Business Source License, please visit our FAQ.
|
|
43
|
+
(see LICENSING.md in this repository)
|
|
44
|
+
Change Date: Four years from the date the Licensed Work is published.
|
|
45
|
+
Change License: Apache License, Version 2.0
|
|
46
|
+
|
|
47
|
+
For information about alternative licensing arrangements for the Licensed Work,
|
|
48
|
+
please contact licensing@objectstack.dev.
|
|
49
|
+
|
|
50
|
+
Notice
|
|
51
|
+
|
|
52
|
+
Business Source License 1.1
|
|
53
|
+
|
|
54
|
+
Terms
|
|
55
|
+
|
|
56
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
57
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
58
|
+
Licensor may make an Additional Use Grant, above, permitting limited production use.
|
|
59
|
+
|
|
60
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
61
|
+
available distribution of a specific version of the Licensed Work under this
|
|
62
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
63
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
64
|
+
above terminate.
|
|
65
|
+
|
|
66
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
67
|
+
currently in effect as described in this License, you must purchase a
|
|
68
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
69
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
70
|
+
|
|
71
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
72
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
73
|
+
separately for each version of the Licensed Work and the Change Date may vary
|
|
74
|
+
for each version of the Licensed Work released by Licensor.
|
|
75
|
+
|
|
76
|
+
You must conspicuously display this License on each original or modified copy
|
|
77
|
+
of the Licensed Work. If you receive the Licensed Work in original or
|
|
78
|
+
modified form from a third party, the terms and conditions set forth in this
|
|
79
|
+
License apply to your use of that work.
|
|
80
|
+
|
|
81
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
82
|
+
terminate your rights under this License for the current and all other
|
|
83
|
+
versions of the Licensed Work.
|
|
84
|
+
|
|
85
|
+
This License does not grant you any right in any trademark or logo of
|
|
86
|
+
Licensor or its affiliates (provided that you may use a trademark or logo of
|
|
87
|
+
Licensor as expressly required by this License).
|
|
88
|
+
|
|
89
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
90
|
+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
91
|
+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
|
92
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
|
93
|
+
TITLE.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Connector } from '@objectstack/spec/integration';
|
|
2
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MCP connector — a *generic* adapter that turns any Model Context Protocol
|
|
6
|
+
* server into a {@link Connector} (ADR-0024). Where `connector-rest` and
|
|
7
|
+
* `connector-slack` are concrete, per-service connectors, this one is a single
|
|
8
|
+
* adapter that adopts the entire MCP ecosystem with **no per-server code**:
|
|
9
|
+
*
|
|
10
|
+
* 1. connect to the MCP server over the configured transport,
|
|
11
|
+
* 2. call `tools/list` and map each tool to a connector action
|
|
12
|
+
* (`name → key`, `description → label/description`, `inputSchema → inputSchema`),
|
|
13
|
+
* 3. build an ordinary `type: 'api'` {@link Connector} once, and
|
|
14
|
+
* 4. dispatch each `connector_action` call to the server's `tools/call`.
|
|
15
|
+
*
|
|
16
|
+
* After construction the registry, the `connector_action` node, the discovery
|
|
17
|
+
* route, and the Studio palette all see a plain connector — they never know it
|
|
18
|
+
* is backed by MCP (ADR-0024 §2).
|
|
19
|
+
*
|
|
20
|
+
* **Credentials live with the MCP server, not in `ConnectorSchema`** (ADR-0024
|
|
21
|
+
* §3). The operator supplies `env` (stdio) / `headers` (http) which we pass
|
|
22
|
+
* straight to the transport; they are never copied into the serialized `def`
|
|
23
|
+
* (which is exposed via discovery) and must never be logged.
|
|
24
|
+
*
|
|
25
|
+
* **Trust:** launching a stdio server runs a local process. Sandboxed,
|
|
26
|
+
* multi-tenant execution and managed secrets are the enterprise tier (ADR-0024
|
|
27
|
+
* §4); the open adapter runs an operator-provided server with operator-provided
|
|
28
|
+
* credentials and documents that trust assumption.
|
|
29
|
+
*/
|
|
30
|
+
/** How to reach the MCP server. */
|
|
31
|
+
type McpTransport = {
|
|
32
|
+
kind: 'stdio';
|
|
33
|
+
/** Executable to launch (e.g. `npx`). */
|
|
34
|
+
command: string;
|
|
35
|
+
/** Arguments passed to the command. */
|
|
36
|
+
args?: string[];
|
|
37
|
+
/** Environment variables for the child process — carries credentials. */
|
|
38
|
+
env?: Record<string, string>;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'http';
|
|
41
|
+
/** Streamable-HTTP endpoint of the MCP server. */
|
|
42
|
+
url: string;
|
|
43
|
+
/** Headers sent on every request — carries credentials (e.g. a bearer token). */
|
|
44
|
+
headers?: Record<string, string>;
|
|
45
|
+
};
|
|
46
|
+
/** A tool as advertised by an MCP server's `tools/list`. */
|
|
47
|
+
interface McpToolDescriptor {
|
|
48
|
+
name: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
/** JSON Schema for the tool's arguments. */
|
|
51
|
+
inputSchema?: Record<string, unknown>;
|
|
52
|
+
/** JSON Schema for the tool's result (optional — many servers omit it). */
|
|
53
|
+
outputSchema?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The minimal slice of an MCP client the adapter needs. Kept structural so
|
|
57
|
+
* tests can inject a fake and the real SDK stays an implementation detail
|
|
58
|
+
* (mirrors `fetchImpl` injection in `connector-rest`).
|
|
59
|
+
*/
|
|
60
|
+
interface McpClientLike {
|
|
61
|
+
/** List the server's tools (`tools/list`). */
|
|
62
|
+
listTools(): Promise<McpToolDescriptor[]>;
|
|
63
|
+
/** Invoke a tool (`tools/call`); returns the raw MCP result. */
|
|
64
|
+
callTool(name: string, args: Record<string, unknown>): Promise<unknown>;
|
|
65
|
+
/** Close the connection / tear down the transport. */
|
|
66
|
+
close(): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
interface McpConnectorOptions {
|
|
69
|
+
/** Connector machine name (snake_case). Defaults to a slug of `label`, else `mcp`. */
|
|
70
|
+
name?: string;
|
|
71
|
+
/** Human-readable label. Defaults to a title derived from `name`. */
|
|
72
|
+
label?: string;
|
|
73
|
+
/** Connector description for the palette. */
|
|
74
|
+
description?: string;
|
|
75
|
+
/** Icon identifier. Defaults to `plug`. */
|
|
76
|
+
icon?: string;
|
|
77
|
+
/** How to reach the MCP server. */
|
|
78
|
+
transport: McpTransport;
|
|
79
|
+
/** Only expose tools whose name matches (allowlist) — keeps the palette lean. */
|
|
80
|
+
include?: (toolName: string) => boolean;
|
|
81
|
+
/** Identifies this client to the MCP server during the handshake. */
|
|
82
|
+
clientInfo?: {
|
|
83
|
+
name: string;
|
|
84
|
+
version: string;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Injected for tests; defaults to the real SDK-backed client. Receives the
|
|
88
|
+
* configured transport and returns a connected {@link McpClientLike}.
|
|
89
|
+
*/
|
|
90
|
+
clientFactory?: (transport: McpTransport, clientInfo: {
|
|
91
|
+
name: string;
|
|
92
|
+
version: string;
|
|
93
|
+
}) => Promise<McpClientLike>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A connector definition + handlers, ready for `engine.registerConnector()`,
|
|
97
|
+
* plus a `close()` for the connection lifecycle (called by the plugin's stop()).
|
|
98
|
+
*/
|
|
99
|
+
interface McpConnectorBundle {
|
|
100
|
+
def: Connector;
|
|
101
|
+
handlers: Record<string, (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>>;
|
|
102
|
+
/** Tear down the MCP client/connection. */
|
|
103
|
+
close(): Promise<void>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Connect to an MCP server, discover its tools, and build a {@link Connector}
|
|
107
|
+
* whose actions dispatch to the server's `tools/call`. The connection is held
|
|
108
|
+
* open for the lifetime of the bundle; call {@link McpConnectorBundle.close} to
|
|
109
|
+
* tear it down.
|
|
110
|
+
*/
|
|
111
|
+
declare function createMcpConnector(opts: McpConnectorOptions): Promise<McpConnectorBundle>;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 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`.
|
|
117
|
+
*/
|
|
118
|
+
interface ConnectorRegistrySurface {
|
|
119
|
+
registerConnector(def: Connector, handlers: Record<string, (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>>): void;
|
|
120
|
+
unregisterConnector(name: string): void;
|
|
121
|
+
}
|
|
122
|
+
interface ConnectorMcpPluginOptions extends McpConnectorOptions {
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
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.
|
|
129
|
+
*
|
|
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`).
|
|
134
|
+
*/
|
|
135
|
+
declare class ConnectorMcpPlugin implements Plugin {
|
|
136
|
+
name: string;
|
|
137
|
+
version: string;
|
|
138
|
+
type: "standard";
|
|
139
|
+
dependencies: string[];
|
|
140
|
+
private readonly options;
|
|
141
|
+
private connectorName?;
|
|
142
|
+
private automation?;
|
|
143
|
+
private close?;
|
|
144
|
+
constructor(options: ConnectorMcpPluginOptions);
|
|
145
|
+
init(_ctx: PluginContext): Promise<void>;
|
|
146
|
+
start(ctx: PluginContext): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Destroy phase — the kernel's shutdown hook (the `Plugin` lifecycle exposes
|
|
149
|
+
* `destroy()`, not `stop()`). Unregister the connector and tear the MCP
|
|
150
|
+
* connection down so no child process / socket is leaked.
|
|
151
|
+
*/
|
|
152
|
+
destroy(): Promise<void>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export { ConnectorMcpPlugin, type ConnectorMcpPluginOptions, type ConnectorRegistrySurface, type McpClientLike, type McpConnectorBundle, type McpConnectorOptions, type McpToolDescriptor, type McpTransport, createMcpConnector };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Connector } from '@objectstack/spec/integration';
|
|
2
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MCP connector — a *generic* adapter that turns any Model Context Protocol
|
|
6
|
+
* server into a {@link Connector} (ADR-0024). Where `connector-rest` and
|
|
7
|
+
* `connector-slack` are concrete, per-service connectors, this one is a single
|
|
8
|
+
* adapter that adopts the entire MCP ecosystem with **no per-server code**:
|
|
9
|
+
*
|
|
10
|
+
* 1. connect to the MCP server over the configured transport,
|
|
11
|
+
* 2. call `tools/list` and map each tool to a connector action
|
|
12
|
+
* (`name → key`, `description → label/description`, `inputSchema → inputSchema`),
|
|
13
|
+
* 3. build an ordinary `type: 'api'` {@link Connector} once, and
|
|
14
|
+
* 4. dispatch each `connector_action` call to the server's `tools/call`.
|
|
15
|
+
*
|
|
16
|
+
* After construction the registry, the `connector_action` node, the discovery
|
|
17
|
+
* route, and the Studio palette all see a plain connector — they never know it
|
|
18
|
+
* is backed by MCP (ADR-0024 §2).
|
|
19
|
+
*
|
|
20
|
+
* **Credentials live with the MCP server, not in `ConnectorSchema`** (ADR-0024
|
|
21
|
+
* §3). The operator supplies `env` (stdio) / `headers` (http) which we pass
|
|
22
|
+
* straight to the transport; they are never copied into the serialized `def`
|
|
23
|
+
* (which is exposed via discovery) and must never be logged.
|
|
24
|
+
*
|
|
25
|
+
* **Trust:** launching a stdio server runs a local process. Sandboxed,
|
|
26
|
+
* multi-tenant execution and managed secrets are the enterprise tier (ADR-0024
|
|
27
|
+
* §4); the open adapter runs an operator-provided server with operator-provided
|
|
28
|
+
* credentials and documents that trust assumption.
|
|
29
|
+
*/
|
|
30
|
+
/** How to reach the MCP server. */
|
|
31
|
+
type McpTransport = {
|
|
32
|
+
kind: 'stdio';
|
|
33
|
+
/** Executable to launch (e.g. `npx`). */
|
|
34
|
+
command: string;
|
|
35
|
+
/** Arguments passed to the command. */
|
|
36
|
+
args?: string[];
|
|
37
|
+
/** Environment variables for the child process — carries credentials. */
|
|
38
|
+
env?: Record<string, string>;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'http';
|
|
41
|
+
/** Streamable-HTTP endpoint of the MCP server. */
|
|
42
|
+
url: string;
|
|
43
|
+
/** Headers sent on every request — carries credentials (e.g. a bearer token). */
|
|
44
|
+
headers?: Record<string, string>;
|
|
45
|
+
};
|
|
46
|
+
/** A tool as advertised by an MCP server's `tools/list`. */
|
|
47
|
+
interface McpToolDescriptor {
|
|
48
|
+
name: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
/** JSON Schema for the tool's arguments. */
|
|
51
|
+
inputSchema?: Record<string, unknown>;
|
|
52
|
+
/** JSON Schema for the tool's result (optional — many servers omit it). */
|
|
53
|
+
outputSchema?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The minimal slice of an MCP client the adapter needs. Kept structural so
|
|
57
|
+
* tests can inject a fake and the real SDK stays an implementation detail
|
|
58
|
+
* (mirrors `fetchImpl` injection in `connector-rest`).
|
|
59
|
+
*/
|
|
60
|
+
interface McpClientLike {
|
|
61
|
+
/** List the server's tools (`tools/list`). */
|
|
62
|
+
listTools(): Promise<McpToolDescriptor[]>;
|
|
63
|
+
/** Invoke a tool (`tools/call`); returns the raw MCP result. */
|
|
64
|
+
callTool(name: string, args: Record<string, unknown>): Promise<unknown>;
|
|
65
|
+
/** Close the connection / tear down the transport. */
|
|
66
|
+
close(): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
interface McpConnectorOptions {
|
|
69
|
+
/** Connector machine name (snake_case). Defaults to a slug of `label`, else `mcp`. */
|
|
70
|
+
name?: string;
|
|
71
|
+
/** Human-readable label. Defaults to a title derived from `name`. */
|
|
72
|
+
label?: string;
|
|
73
|
+
/** Connector description for the palette. */
|
|
74
|
+
description?: string;
|
|
75
|
+
/** Icon identifier. Defaults to `plug`. */
|
|
76
|
+
icon?: string;
|
|
77
|
+
/** How to reach the MCP server. */
|
|
78
|
+
transport: McpTransport;
|
|
79
|
+
/** Only expose tools whose name matches (allowlist) — keeps the palette lean. */
|
|
80
|
+
include?: (toolName: string) => boolean;
|
|
81
|
+
/** Identifies this client to the MCP server during the handshake. */
|
|
82
|
+
clientInfo?: {
|
|
83
|
+
name: string;
|
|
84
|
+
version: string;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Injected for tests; defaults to the real SDK-backed client. Receives the
|
|
88
|
+
* configured transport and returns a connected {@link McpClientLike}.
|
|
89
|
+
*/
|
|
90
|
+
clientFactory?: (transport: McpTransport, clientInfo: {
|
|
91
|
+
name: string;
|
|
92
|
+
version: string;
|
|
93
|
+
}) => Promise<McpClientLike>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A connector definition + handlers, ready for `engine.registerConnector()`,
|
|
97
|
+
* plus a `close()` for the connection lifecycle (called by the plugin's stop()).
|
|
98
|
+
*/
|
|
99
|
+
interface McpConnectorBundle {
|
|
100
|
+
def: Connector;
|
|
101
|
+
handlers: Record<string, (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>>;
|
|
102
|
+
/** Tear down the MCP client/connection. */
|
|
103
|
+
close(): Promise<void>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Connect to an MCP server, discover its tools, and build a {@link Connector}
|
|
107
|
+
* whose actions dispatch to the server's `tools/call`. The connection is held
|
|
108
|
+
* open for the lifetime of the bundle; call {@link McpConnectorBundle.close} to
|
|
109
|
+
* tear it down.
|
|
110
|
+
*/
|
|
111
|
+
declare function createMcpConnector(opts: McpConnectorOptions): Promise<McpConnectorBundle>;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 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`.
|
|
117
|
+
*/
|
|
118
|
+
interface ConnectorRegistrySurface {
|
|
119
|
+
registerConnector(def: Connector, handlers: Record<string, (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>>): void;
|
|
120
|
+
unregisterConnector(name: string): void;
|
|
121
|
+
}
|
|
122
|
+
interface ConnectorMcpPluginOptions extends McpConnectorOptions {
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
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.
|
|
129
|
+
*
|
|
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`).
|
|
134
|
+
*/
|
|
135
|
+
declare class ConnectorMcpPlugin implements Plugin {
|
|
136
|
+
name: string;
|
|
137
|
+
version: string;
|
|
138
|
+
type: "standard";
|
|
139
|
+
dependencies: string[];
|
|
140
|
+
private readonly options;
|
|
141
|
+
private connectorName?;
|
|
142
|
+
private automation?;
|
|
143
|
+
private close?;
|
|
144
|
+
constructor(options: ConnectorMcpPluginOptions);
|
|
145
|
+
init(_ctx: PluginContext): Promise<void>;
|
|
146
|
+
start(ctx: PluginContext): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Destroy phase — the kernel's shutdown hook (the `Plugin` lifecycle exposes
|
|
149
|
+
* `destroy()`, not `stop()`). Unregister the connector and tear the MCP
|
|
150
|
+
* connection down so no child process / socket is leaked.
|
|
151
|
+
*/
|
|
152
|
+
destroy(): Promise<void>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export { ConnectorMcpPlugin, type ConnectorMcpPluginOptions, type ConnectorRegistrySurface, type McpClientLike, type McpConnectorBundle, type McpConnectorOptions, type McpToolDescriptor, type McpTransport, createMcpConnector };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ConnectorMcpPlugin: () => ConnectorMcpPlugin,
|
|
34
|
+
createMcpConnector: () => createMcpConnector
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/mcp-connector.ts
|
|
39
|
+
var DEFAULT_CLIENT_INFO = { name: "objectstack-connector-mcp", version: "1.0.0" };
|
|
40
|
+
function slugify(input) {
|
|
41
|
+
const slug = input.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
42
|
+
if (!slug) return "mcp";
|
|
43
|
+
return /^[a-z_]/.test(slug) ? slug : `mcp_${slug}`;
|
|
44
|
+
}
|
|
45
|
+
function titleize(name) {
|
|
46
|
+
return name.split("_").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
47
|
+
}
|
|
48
|
+
function normalizeResult(raw) {
|
|
49
|
+
const result = raw ?? {};
|
|
50
|
+
const isError = result.isError === true;
|
|
51
|
+
const out = {
|
|
52
|
+
ok: !isError,
|
|
53
|
+
content: result.content ?? []
|
|
54
|
+
};
|
|
55
|
+
if (result.structuredContent !== void 0) out.structuredContent = result.structuredContent;
|
|
56
|
+
if (isError) out.isError = true;
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
async function defaultClientFactory(transport, clientInfo) {
|
|
60
|
+
const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
|
|
61
|
+
const client = new Client(clientInfo, { capabilities: {} });
|
|
62
|
+
if (transport.kind === "stdio") {
|
|
63
|
+
const { StdioClientTransport } = await import("@modelcontextprotocol/sdk/client/stdio.js");
|
|
64
|
+
await client.connect(
|
|
65
|
+
new StdioClientTransport({
|
|
66
|
+
command: transport.command,
|
|
67
|
+
args: transport.args,
|
|
68
|
+
env: transport.env
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
} else {
|
|
72
|
+
const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
73
|
+
await client.connect(
|
|
74
|
+
new StreamableHTTPClientTransport(new URL(transport.url), {
|
|
75
|
+
requestInit: transport.headers ? { headers: transport.headers } : void 0
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
async listTools() {
|
|
81
|
+
const res = await client.listTools();
|
|
82
|
+
return res.tools ?? [];
|
|
83
|
+
},
|
|
84
|
+
async callTool(name, args) {
|
|
85
|
+
return client.callTool({ name, arguments: args });
|
|
86
|
+
},
|
|
87
|
+
async close() {
|
|
88
|
+
await client.close();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function createMcpConnector(opts) {
|
|
93
|
+
const clientInfo = opts.clientInfo ?? DEFAULT_CLIENT_INFO;
|
|
94
|
+
const factory = opts.clientFactory ?? defaultClientFactory;
|
|
95
|
+
const client = await factory(opts.transport, clientInfo);
|
|
96
|
+
let tools;
|
|
97
|
+
try {
|
|
98
|
+
tools = await client.listTools();
|
|
99
|
+
} catch (err) {
|
|
100
|
+
await client.close().catch(() => {
|
|
101
|
+
});
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
const include = opts.include ?? (() => true);
|
|
105
|
+
const selected = tools.filter((t) => include(t.name));
|
|
106
|
+
const name = opts.name ?? slugify(opts.label ?? "mcp");
|
|
107
|
+
const label = opts.label ?? titleize(name);
|
|
108
|
+
const handlers = {};
|
|
109
|
+
const def = {
|
|
110
|
+
name,
|
|
111
|
+
label,
|
|
112
|
+
type: "api",
|
|
113
|
+
description: opts.description ?? `MCP connector exposing ${selected.length} tool(s) from a Model Context Protocol server.`,
|
|
114
|
+
icon: opts.icon ?? "plug",
|
|
115
|
+
// MCP servers own their own auth (passed via transport env/headers); we
|
|
116
|
+
// do not model the upstream's credentials in ConnectorSchema (ADR-0024 §3).
|
|
117
|
+
authentication: { type: "none" },
|
|
118
|
+
// Defaulted by ConnectorSchema; set explicitly so the literal satisfies
|
|
119
|
+
// the (post-parse) Connector output type.
|
|
120
|
+
status: "active",
|
|
121
|
+
enabled: true,
|
|
122
|
+
connectionTimeoutMs: 3e4,
|
|
123
|
+
requestTimeoutMs: 3e4,
|
|
124
|
+
actions: selected.map((tool) => ({
|
|
125
|
+
key: tool.name,
|
|
126
|
+
// MCP tool names are machine names; derive a readable label and keep
|
|
127
|
+
// the server's description verbatim (ADR-0024 `description → label/description`).
|
|
128
|
+
label: titleize(slugify(tool.name)),
|
|
129
|
+
description: tool.description,
|
|
130
|
+
// The MCP inputSchema is already JSON Schema — pass it straight through.
|
|
131
|
+
inputSchema: tool.inputSchema,
|
|
132
|
+
// Many servers omit outputSchema; leave it unset when absent (as the
|
|
133
|
+
// REST connector does for untyped responses).
|
|
134
|
+
outputSchema: tool.outputSchema
|
|
135
|
+
}))
|
|
136
|
+
};
|
|
137
|
+
for (const tool of selected) {
|
|
138
|
+
handlers[tool.name] = async (input) => normalizeResult(await client.callTool(tool.name, input));
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
def,
|
|
142
|
+
handlers,
|
|
143
|
+
close: () => client.close()
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/connector-mcp-plugin.ts
|
|
148
|
+
var ConnectorMcpPlugin = class {
|
|
149
|
+
constructor(options) {
|
|
150
|
+
this.name = "com.objectstack.connector.mcp";
|
|
151
|
+
this.version = "1.0.0";
|
|
152
|
+
this.type = "standard";
|
|
153
|
+
// Ensure the automation engine (and its connector registry) is started first.
|
|
154
|
+
this.dependencies = ["com.objectstack.service-automation"];
|
|
155
|
+
this.options = options;
|
|
156
|
+
}
|
|
157
|
+
async init(_ctx) {
|
|
158
|
+
}
|
|
159
|
+
async start(ctx) {
|
|
160
|
+
let automation;
|
|
161
|
+
try {
|
|
162
|
+
automation = ctx.getService("automation");
|
|
163
|
+
} catch {
|
|
164
|
+
automation = void 0;
|
|
165
|
+
}
|
|
166
|
+
if (!automation || typeof automation.registerConnector !== "function") {
|
|
167
|
+
ctx.logger.info("ConnectorMcpPlugin: no automation engine \u2014 MCP connector not registered");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
let bundle;
|
|
171
|
+
try {
|
|
172
|
+
bundle = await createMcpConnector(this.options);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
ctx.logger.warn(
|
|
175
|
+
`ConnectorMcpPlugin: could not connect to MCP server \u2014 connector not registered: ${err.message}`
|
|
176
|
+
);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
automation.registerConnector(bundle.def, bundle.handlers);
|
|
180
|
+
this.automation = automation;
|
|
181
|
+
this.connectorName = bundle.def.name;
|
|
182
|
+
this.close = bundle.close;
|
|
183
|
+
ctx.logger.info(
|
|
184
|
+
`ConnectorMcpPlugin: MCP connector '${bundle.def.name}' registered with ${bundle.def.actions?.length ?? 0} action(s)`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Destroy phase — the kernel's shutdown hook (the `Plugin` lifecycle exposes
|
|
189
|
+
* `destroy()`, not `stop()`). Unregister the connector and tear the MCP
|
|
190
|
+
* connection down so no child process / socket is leaked.
|
|
191
|
+
*/
|
|
192
|
+
async destroy() {
|
|
193
|
+
if (this.automation && this.connectorName) {
|
|
194
|
+
try {
|
|
195
|
+
this.automation.unregisterConnector(this.connectorName);
|
|
196
|
+
} catch {
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (this.close) {
|
|
200
|
+
try {
|
|
201
|
+
await this.close();
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
208
|
+
0 && (module.exports = {
|
|
209
|
+
ConnectorMcpPlugin,
|
|
210
|
+
createMcpConnector
|
|
211
|
+
});
|
|
212
|
+
//# sourceMappingURL=index.js.map
|