@contractspec/example.integration-hub 2.5.0 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/dist/docs/index.js +2 -0
- package/dist/docs/integration-hub.docblock.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +149 -0
- package/dist/mcp-example.d.ts +16 -0
- package/dist/mcp-example.js +151 -0
- package/dist/node/docs/index.js +2 -0
- package/dist/node/docs/integration-hub.docblock.js +2 -0
- package/dist/node/index.js +149 -0
- package/dist/node/mcp-example.js +150 -0
- package/dist/node/run-mcp.js +155 -0
- package/dist/run-mcp.d.ts +1 -0
- package/dist/run-mcp.js +156 -0
- package/package.json +34 -94
- package/dist/browser/connection/connection.enum.js +0 -12
- package/dist/browser/connection/connection.operation.js +0 -101
- package/dist/browser/connection/connection.presentation.js +0 -102
- package/dist/browser/connection/connection.schema.js +0 -48
- package/dist/browser/connection/index.js +0 -104
- package/dist/browser/docs/index.js +0 -104
- package/dist/browser/docs/integration-hub.docblock.js +0 -104
- package/dist/browser/events.js +0 -211
- package/dist/browser/example.js +0 -42
- package/dist/browser/handlers/index.js +0 -246
- package/dist/browser/handlers/integration.handlers.js +0 -246
- package/dist/browser/index.js +0 -1595
- package/dist/browser/integration/index.js +0 -92
- package/dist/browser/integration/integration.enum.js +0 -12
- package/dist/browser/integration/integration.operations.js +0 -89
- package/dist/browser/integration/integration.presentation.js +0 -120
- package/dist/browser/integration/integration.schema.js +0 -42
- package/dist/browser/integration-hub.capability.js +0 -43
- package/dist/browser/integration-hub.feature.js +0 -114
- package/dist/browser/seeders/index.js +0 -60
- package/dist/browser/sync/index.js +0 -332
- package/dist/browser/sync/sync.enum.js +0 -26
- package/dist/browser/sync/sync.operations.js +0 -321
- package/dist/browser/sync/sync.presentation.js +0 -301
- package/dist/browser/sync/sync.schema.js +0 -154
- package/dist/browser/sync-engine/index.js +0 -186
- package/dist/browser/tests/operations.test-spec.js +0 -85
- package/dist/browser/ui/IntegrationDashboard.js +0 -369
- package/dist/browser/ui/hooks/index.js +0 -57
- package/dist/browser/ui/hooks/useIntegrationData.js +0 -54
- package/dist/browser/ui/index.js +0 -644
- package/dist/browser/ui/renderers/index.js +0 -273
- package/dist/browser/ui/renderers/integration.markdown.js +0 -273
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ A comprehensive integration hub example demonstrating ContractSpec principles fo
|
|
|
15
15
|
- **Scheduled Sync**: Cron-based scheduled synchronization
|
|
16
16
|
- **Feature Flag Integration**: Control integration availability
|
|
17
17
|
- **Full Audit Trail**: Track all sync operations
|
|
18
|
+
- **MCP-Ready Providers**: Supports provider configs that call remote MCP tools
|
|
18
19
|
|
|
19
20
|
## Entities
|
|
20
21
|
|
|
@@ -137,6 +138,45 @@ const run = await executeContract(TriggerSyncContract, {
|
|
|
137
138
|
});
|
|
138
139
|
```
|
|
139
140
|
|
|
141
|
+
## MCP Provider Wiring
|
|
142
|
+
|
|
143
|
+
Provider adapters in this workspace can be configured to call MCP endpoints
|
|
144
|
+
(for example `mcpUrl` + headers/tokens on analytics providers, or transport
|
|
145
|
+
selection for meeting recorder providers). This example remains provider-agnostic,
|
|
146
|
+
so MCP transport details stay in provider config rather than contract logic.
|
|
147
|
+
|
|
148
|
+
## Run MCP Example
|
|
149
|
+
|
|
150
|
+
This package now includes a runnable MCP connectivity example:
|
|
151
|
+
`src/run-mcp.ts`.
|
|
152
|
+
|
|
153
|
+
List MCP tools from a local stdio server (default):
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
export CONTRACTSPEC_INTEGRATION_HUB_MCP_TRANSPORT="stdio"
|
|
157
|
+
export CONTRACTSPEC_INTEGRATION_HUB_MCP_COMMAND="npx"
|
|
158
|
+
export CONTRACTSPEC_INTEGRATION_HUB_MCP_ARGS_JSON='["-y","@modelcontextprotocol/server-filesystem","."]'
|
|
159
|
+
|
|
160
|
+
bun run --filter @contractspec/example.integration-hub run:mcp
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Call a specific MCP tool:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
export CONTRACTSPEC_INTEGRATION_HUB_MCP_MODE="call"
|
|
167
|
+
export CONTRACTSPEC_INTEGRATION_HUB_MCP_TOOL_NAME="read_file"
|
|
168
|
+
export CONTRACTSPEC_INTEGRATION_HUB_MCP_TOOL_ARGS_JSON='{"path":"README.md"}'
|
|
169
|
+
|
|
170
|
+
bun run --filter @contractspec/example.integration-hub run:mcp
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Remote MCP transport is also supported via:
|
|
174
|
+
|
|
175
|
+
- `CONTRACTSPEC_INTEGRATION_HUB_MCP_TRANSPORT=http|sse`
|
|
176
|
+
- `CONTRACTSPEC_INTEGRATION_HUB_MCP_URL`
|
|
177
|
+
- `CONTRACTSPEC_INTEGRATION_HUB_MCP_HEADERS_JSON` (optional)
|
|
178
|
+
- `CONTRACTSPEC_INTEGRATION_HUB_MCP_ACCESS_TOKEN` or `..._ACCESS_TOKEN_ENV` (optional)
|
|
179
|
+
|
|
140
180
|
## Dependencies
|
|
141
181
|
|
|
142
182
|
- `@contractspec/lib.identity-rbac` - User identity and roles
|
package/dist/docs/index.js
CHANGED
|
@@ -68,6 +68,7 @@ var integrationHubDocBlocks = [
|
|
|
68
68
|
body: `## Setup
|
|
69
69
|
1) Seed integrations/connections (if available) or create connector definitions.
|
|
70
70
|
2) Configure sync jobs with Jobs module; store payload archives via Files.
|
|
71
|
+
3) Use \`src/run-mcp.ts\` to validate MCP connectivity for provider adapters.
|
|
71
72
|
|
|
72
73
|
## Extend & regenerate
|
|
73
74
|
1) Add mapping fields or provider configs in the spec; include validation and PII paths.
|
|
@@ -95,6 +96,7 @@ var integrationHubDocBlocks = [
|
|
|
95
96
|
## PII & Payloads
|
|
96
97
|
- Treat payload archives as potentially sensitive; mark policy.pii paths.
|
|
97
98
|
- For MCP/web, avoid exposing raw credentials/tokens; store via provider adapters only.
|
|
99
|
+
- Keep MCP endpoint URLs, headers, and tokens in provider connection config, never in mappings.
|
|
98
100
|
|
|
99
101
|
## Verification
|
|
100
102
|
- Include fixtures for mapping changes and sync retries.
|
|
@@ -68,6 +68,7 @@ var integrationHubDocBlocks = [
|
|
|
68
68
|
body: `## Setup
|
|
69
69
|
1) Seed integrations/connections (if available) or create connector definitions.
|
|
70
70
|
2) Configure sync jobs with Jobs module; store payload archives via Files.
|
|
71
|
+
3) Use \`src/run-mcp.ts\` to validate MCP connectivity for provider adapters.
|
|
71
72
|
|
|
72
73
|
## Extend & regenerate
|
|
73
74
|
1) Add mapping fields or provider configs in the spec; include validation and PII paths.
|
|
@@ -95,6 +96,7 @@ var integrationHubDocBlocks = [
|
|
|
95
96
|
## PII & Payloads
|
|
96
97
|
- Treat payload archives as potentially sensitive; mark policy.pii paths.
|
|
97
98
|
- For MCP/web, avoid exposing raw credentials/tokens; store via provider adapters only.
|
|
99
|
+
- Keep MCP endpoint URLs, headers, and tokens in provider connection config, never in mappings.
|
|
98
100
|
|
|
99
101
|
## Verification
|
|
100
102
|
- Include fixtures for mapping changes and sync retries.
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1558,9 +1558,158 @@ function hasChanges(sourceChecksum, targetChecksum) {
|
|
|
1558
1558
|
}
|
|
1559
1559
|
return sourceChecksum !== targetChecksum;
|
|
1560
1560
|
}
|
|
1561
|
+
|
|
1562
|
+
// src/mcp-example.ts
|
|
1563
|
+
import { randomUUID } from "crypto";
|
|
1564
|
+
import {
|
|
1565
|
+
createMcpToolsets
|
|
1566
|
+
} from "@contractspec/lib.ai-agent/tools/mcp-client";
|
|
1567
|
+
var DEFAULT_STDIO_ARGS = [
|
|
1568
|
+
"-y",
|
|
1569
|
+
"@modelcontextprotocol/server-filesystem",
|
|
1570
|
+
"."
|
|
1571
|
+
];
|
|
1572
|
+
async function runIntegrationHubMcpExampleFromEnv() {
|
|
1573
|
+
const mode = resolveMode();
|
|
1574
|
+
const config = buildMcpConfigFromEnv();
|
|
1575
|
+
const toolset = await createMcpToolsets([config], {
|
|
1576
|
+
onNameCollision: "error"
|
|
1577
|
+
});
|
|
1578
|
+
try {
|
|
1579
|
+
const toolNames = Object.keys(toolset.tools).sort();
|
|
1580
|
+
const output = {
|
|
1581
|
+
mode,
|
|
1582
|
+
server: {
|
|
1583
|
+
name: config.name,
|
|
1584
|
+
transport: config.transport ?? "stdio"
|
|
1585
|
+
},
|
|
1586
|
+
tools: toolNames
|
|
1587
|
+
};
|
|
1588
|
+
if (mode === "call") {
|
|
1589
|
+
const toolName = requireEnv("CONTRACTSPEC_INTEGRATION_HUB_MCP_TOOL_NAME");
|
|
1590
|
+
const toolArgs = parseRecordEnvOrDefault("CONTRACTSPEC_INTEGRATION_HUB_MCP_TOOL_ARGS_JSON", {});
|
|
1591
|
+
const tool = toolset.tools[toolName];
|
|
1592
|
+
if (!tool?.execute) {
|
|
1593
|
+
throw new Error(`Tool "${toolName}" was not found. Available tools: ${toolNames.join(", ")}`);
|
|
1594
|
+
}
|
|
1595
|
+
const toolOutput = await tool.execute(toolArgs, {
|
|
1596
|
+
toolCallId: `integration-hub-${randomUUID()}`,
|
|
1597
|
+
messages: []
|
|
1598
|
+
});
|
|
1599
|
+
output.toolCall = {
|
|
1600
|
+
name: toolName,
|
|
1601
|
+
args: toolArgs,
|
|
1602
|
+
output: toolOutput
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
return output;
|
|
1606
|
+
} finally {
|
|
1607
|
+
await toolset.cleanup().catch(() => {
|
|
1608
|
+
return;
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
function buildMcpConfigFromEnv() {
|
|
1613
|
+
const name = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_NAME ?? "filesystem";
|
|
1614
|
+
const transport = resolveTransport();
|
|
1615
|
+
const toolPrefix = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_TOOL_PREFIX;
|
|
1616
|
+
if (transport === "stdio") {
|
|
1617
|
+
return {
|
|
1618
|
+
name,
|
|
1619
|
+
transport,
|
|
1620
|
+
command: process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_COMMAND ?? "npx",
|
|
1621
|
+
args: parseStringArrayEnv("CONTRACTSPEC_INTEGRATION_HUB_MCP_ARGS_JSON", DEFAULT_STDIO_ARGS),
|
|
1622
|
+
toolPrefix
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
const accessToken = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_ACCESS_TOKEN;
|
|
1626
|
+
const accessTokenEnvVar = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_ACCESS_TOKEN_ENV;
|
|
1627
|
+
return {
|
|
1628
|
+
name,
|
|
1629
|
+
transport,
|
|
1630
|
+
url: requireEnv("CONTRACTSPEC_INTEGRATION_HUB_MCP_URL"),
|
|
1631
|
+
headers: parseStringRecordEnv("CONTRACTSPEC_INTEGRATION_HUB_MCP_HEADERS_JSON"),
|
|
1632
|
+
accessToken,
|
|
1633
|
+
accessTokenEnvVar,
|
|
1634
|
+
toolPrefix
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
function resolveMode() {
|
|
1638
|
+
const rawMode = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_MODE?.toLowerCase() ?? "list";
|
|
1639
|
+
if (rawMode === "list" || rawMode === "call") {
|
|
1640
|
+
return rawMode;
|
|
1641
|
+
}
|
|
1642
|
+
throw new Error(`Unsupported CONTRACTSPEC_INTEGRATION_HUB_MCP_MODE: ${rawMode}. Use "list" or "call".`);
|
|
1643
|
+
}
|
|
1644
|
+
function resolveTransport() {
|
|
1645
|
+
const rawTransport = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_TRANSPORT?.toLowerCase() ?? "stdio";
|
|
1646
|
+
if (rawTransport === "stdio" || rawTransport === "http" || rawTransport === "sse") {
|
|
1647
|
+
return rawTransport;
|
|
1648
|
+
}
|
|
1649
|
+
throw new Error(`Unsupported CONTRACTSPEC_INTEGRATION_HUB_MCP_TRANSPORT: ${rawTransport}. Use "stdio", "http", or "sse".`);
|
|
1650
|
+
}
|
|
1651
|
+
function parseStringArrayEnv(key, fallback) {
|
|
1652
|
+
const raw = process.env[key];
|
|
1653
|
+
if (!raw) {
|
|
1654
|
+
return fallback;
|
|
1655
|
+
}
|
|
1656
|
+
const parsed = parseJsonEnv(key);
|
|
1657
|
+
if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== "string")) {
|
|
1658
|
+
throw new Error(`${key} must be a JSON string array.`);
|
|
1659
|
+
}
|
|
1660
|
+
return parsed;
|
|
1661
|
+
}
|
|
1662
|
+
function parseRecordEnv(key) {
|
|
1663
|
+
const raw = process.env[key];
|
|
1664
|
+
if (!raw) {
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
const parsed = parseJsonEnv(key);
|
|
1668
|
+
if (!isRecord(parsed)) {
|
|
1669
|
+
throw new Error(`${key} must be a JSON object.`);
|
|
1670
|
+
}
|
|
1671
|
+
return parsed;
|
|
1672
|
+
}
|
|
1673
|
+
function parseRecordEnvOrDefault(key, fallback) {
|
|
1674
|
+
return parseRecordEnv(key) ?? fallback;
|
|
1675
|
+
}
|
|
1676
|
+
function parseStringRecordEnv(key) {
|
|
1677
|
+
const parsed = parseRecordEnv(key);
|
|
1678
|
+
if (!parsed) {
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
const entries = Object.entries(parsed);
|
|
1682
|
+
const invalidEntry = entries.find(([, value]) => typeof value !== "string");
|
|
1683
|
+
if (invalidEntry) {
|
|
1684
|
+
throw new Error(`${key} must contain only string values.`);
|
|
1685
|
+
}
|
|
1686
|
+
return Object.fromEntries(entries);
|
|
1687
|
+
}
|
|
1688
|
+
function parseJsonEnv(key) {
|
|
1689
|
+
const raw = process.env[key];
|
|
1690
|
+
if (!raw) {
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
try {
|
|
1694
|
+
return JSON.parse(raw);
|
|
1695
|
+
} catch {
|
|
1696
|
+
throw new Error(`${key} contains invalid JSON.`);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
function requireEnv(key) {
|
|
1700
|
+
const value = process.env[key];
|
|
1701
|
+
if (!value) {
|
|
1702
|
+
throw new Error(`Missing required env var: ${key}`);
|
|
1703
|
+
}
|
|
1704
|
+
return value;
|
|
1705
|
+
}
|
|
1706
|
+
function isRecord(value) {
|
|
1707
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1708
|
+
}
|
|
1561
1709
|
export {
|
|
1562
1710
|
useIntegrationData,
|
|
1563
1711
|
syncConfigMarkdownRenderer,
|
|
1712
|
+
runIntegrationHubMcpExampleFromEnv,
|
|
1564
1713
|
integrationDashboardMarkdownRenderer,
|
|
1565
1714
|
hasChanges,
|
|
1566
1715
|
createSyncEngine,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type IntegrationHubMcpMode = 'list' | 'call';
|
|
2
|
+
interface IntegrationHubMcpOutput {
|
|
3
|
+
mode: IntegrationHubMcpMode;
|
|
4
|
+
server: {
|
|
5
|
+
name: string;
|
|
6
|
+
transport: 'stdio' | 'http' | 'sse';
|
|
7
|
+
};
|
|
8
|
+
tools: string[];
|
|
9
|
+
toolCall?: {
|
|
10
|
+
name: string;
|
|
11
|
+
args: Record<string, unknown>;
|
|
12
|
+
output: unknown;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export declare function runIntegrationHubMcpExampleFromEnv(): Promise<IntegrationHubMcpOutput>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/mcp-example.ts
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
import {
|
|
5
|
+
createMcpToolsets
|
|
6
|
+
} from "@contractspec/lib.ai-agent/tools/mcp-client";
|
|
7
|
+
var DEFAULT_STDIO_ARGS = [
|
|
8
|
+
"-y",
|
|
9
|
+
"@modelcontextprotocol/server-filesystem",
|
|
10
|
+
"."
|
|
11
|
+
];
|
|
12
|
+
async function runIntegrationHubMcpExampleFromEnv() {
|
|
13
|
+
const mode = resolveMode();
|
|
14
|
+
const config = buildMcpConfigFromEnv();
|
|
15
|
+
const toolset = await createMcpToolsets([config], {
|
|
16
|
+
onNameCollision: "error"
|
|
17
|
+
});
|
|
18
|
+
try {
|
|
19
|
+
const toolNames = Object.keys(toolset.tools).sort();
|
|
20
|
+
const output = {
|
|
21
|
+
mode,
|
|
22
|
+
server: {
|
|
23
|
+
name: config.name,
|
|
24
|
+
transport: config.transport ?? "stdio"
|
|
25
|
+
},
|
|
26
|
+
tools: toolNames
|
|
27
|
+
};
|
|
28
|
+
if (mode === "call") {
|
|
29
|
+
const toolName = requireEnv("CONTRACTSPEC_INTEGRATION_HUB_MCP_TOOL_NAME");
|
|
30
|
+
const toolArgs = parseRecordEnvOrDefault("CONTRACTSPEC_INTEGRATION_HUB_MCP_TOOL_ARGS_JSON", {});
|
|
31
|
+
const tool = toolset.tools[toolName];
|
|
32
|
+
if (!tool?.execute) {
|
|
33
|
+
throw new Error(`Tool "${toolName}" was not found. Available tools: ${toolNames.join(", ")}`);
|
|
34
|
+
}
|
|
35
|
+
const toolOutput = await tool.execute(toolArgs, {
|
|
36
|
+
toolCallId: `integration-hub-${randomUUID()}`,
|
|
37
|
+
messages: []
|
|
38
|
+
});
|
|
39
|
+
output.toolCall = {
|
|
40
|
+
name: toolName,
|
|
41
|
+
args: toolArgs,
|
|
42
|
+
output: toolOutput
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return output;
|
|
46
|
+
} finally {
|
|
47
|
+
await toolset.cleanup().catch(() => {
|
|
48
|
+
return;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function buildMcpConfigFromEnv() {
|
|
53
|
+
const name = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_NAME ?? "filesystem";
|
|
54
|
+
const transport = resolveTransport();
|
|
55
|
+
const toolPrefix = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_TOOL_PREFIX;
|
|
56
|
+
if (transport === "stdio") {
|
|
57
|
+
return {
|
|
58
|
+
name,
|
|
59
|
+
transport,
|
|
60
|
+
command: process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_COMMAND ?? "npx",
|
|
61
|
+
args: parseStringArrayEnv("CONTRACTSPEC_INTEGRATION_HUB_MCP_ARGS_JSON", DEFAULT_STDIO_ARGS),
|
|
62
|
+
toolPrefix
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const accessToken = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_ACCESS_TOKEN;
|
|
66
|
+
const accessTokenEnvVar = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_ACCESS_TOKEN_ENV;
|
|
67
|
+
return {
|
|
68
|
+
name,
|
|
69
|
+
transport,
|
|
70
|
+
url: requireEnv("CONTRACTSPEC_INTEGRATION_HUB_MCP_URL"),
|
|
71
|
+
headers: parseStringRecordEnv("CONTRACTSPEC_INTEGRATION_HUB_MCP_HEADERS_JSON"),
|
|
72
|
+
accessToken,
|
|
73
|
+
accessTokenEnvVar,
|
|
74
|
+
toolPrefix
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function resolveMode() {
|
|
78
|
+
const rawMode = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_MODE?.toLowerCase() ?? "list";
|
|
79
|
+
if (rawMode === "list" || rawMode === "call") {
|
|
80
|
+
return rawMode;
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Unsupported CONTRACTSPEC_INTEGRATION_HUB_MCP_MODE: ${rawMode}. Use "list" or "call".`);
|
|
83
|
+
}
|
|
84
|
+
function resolveTransport() {
|
|
85
|
+
const rawTransport = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_TRANSPORT?.toLowerCase() ?? "stdio";
|
|
86
|
+
if (rawTransport === "stdio" || rawTransport === "http" || rawTransport === "sse") {
|
|
87
|
+
return rawTransport;
|
|
88
|
+
}
|
|
89
|
+
throw new Error(`Unsupported CONTRACTSPEC_INTEGRATION_HUB_MCP_TRANSPORT: ${rawTransport}. Use "stdio", "http", or "sse".`);
|
|
90
|
+
}
|
|
91
|
+
function parseStringArrayEnv(key, fallback) {
|
|
92
|
+
const raw = process.env[key];
|
|
93
|
+
if (!raw) {
|
|
94
|
+
return fallback;
|
|
95
|
+
}
|
|
96
|
+
const parsed = parseJsonEnv(key);
|
|
97
|
+
if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== "string")) {
|
|
98
|
+
throw new Error(`${key} must be a JSON string array.`);
|
|
99
|
+
}
|
|
100
|
+
return parsed;
|
|
101
|
+
}
|
|
102
|
+
function parseRecordEnv(key) {
|
|
103
|
+
const raw = process.env[key];
|
|
104
|
+
if (!raw) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const parsed = parseJsonEnv(key);
|
|
108
|
+
if (!isRecord(parsed)) {
|
|
109
|
+
throw new Error(`${key} must be a JSON object.`);
|
|
110
|
+
}
|
|
111
|
+
return parsed;
|
|
112
|
+
}
|
|
113
|
+
function parseRecordEnvOrDefault(key, fallback) {
|
|
114
|
+
return parseRecordEnv(key) ?? fallback;
|
|
115
|
+
}
|
|
116
|
+
function parseStringRecordEnv(key) {
|
|
117
|
+
const parsed = parseRecordEnv(key);
|
|
118
|
+
if (!parsed) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const entries = Object.entries(parsed);
|
|
122
|
+
const invalidEntry = entries.find(([, value]) => typeof value !== "string");
|
|
123
|
+
if (invalidEntry) {
|
|
124
|
+
throw new Error(`${key} must contain only string values.`);
|
|
125
|
+
}
|
|
126
|
+
return Object.fromEntries(entries);
|
|
127
|
+
}
|
|
128
|
+
function parseJsonEnv(key) {
|
|
129
|
+
const raw = process.env[key];
|
|
130
|
+
if (!raw) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(raw);
|
|
135
|
+
} catch {
|
|
136
|
+
throw new Error(`${key} contains invalid JSON.`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function requireEnv(key) {
|
|
140
|
+
const value = process.env[key];
|
|
141
|
+
if (!value) {
|
|
142
|
+
throw new Error(`Missing required env var: ${key}`);
|
|
143
|
+
}
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
146
|
+
function isRecord(value) {
|
|
147
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
148
|
+
}
|
|
149
|
+
export {
|
|
150
|
+
runIntegrationHubMcpExampleFromEnv
|
|
151
|
+
};
|
package/dist/node/docs/index.js
CHANGED
|
@@ -67,6 +67,7 @@ var integrationHubDocBlocks = [
|
|
|
67
67
|
body: `## Setup
|
|
68
68
|
1) Seed integrations/connections (if available) or create connector definitions.
|
|
69
69
|
2) Configure sync jobs with Jobs module; store payload archives via Files.
|
|
70
|
+
3) Use \`src/run-mcp.ts\` to validate MCP connectivity for provider adapters.
|
|
70
71
|
|
|
71
72
|
## Extend & regenerate
|
|
72
73
|
1) Add mapping fields or provider configs in the spec; include validation and PII paths.
|
|
@@ -94,6 +95,7 @@ var integrationHubDocBlocks = [
|
|
|
94
95
|
## PII & Payloads
|
|
95
96
|
- Treat payload archives as potentially sensitive; mark policy.pii paths.
|
|
96
97
|
- For MCP/web, avoid exposing raw credentials/tokens; store via provider adapters only.
|
|
98
|
+
- Keep MCP endpoint URLs, headers, and tokens in provider connection config, never in mappings.
|
|
97
99
|
|
|
98
100
|
## Verification
|
|
99
101
|
- Include fixtures for mapping changes and sync retries.
|
|
@@ -67,6 +67,7 @@ var integrationHubDocBlocks = [
|
|
|
67
67
|
body: `## Setup
|
|
68
68
|
1) Seed integrations/connections (if available) or create connector definitions.
|
|
69
69
|
2) Configure sync jobs with Jobs module; store payload archives via Files.
|
|
70
|
+
3) Use \`src/run-mcp.ts\` to validate MCP connectivity for provider adapters.
|
|
70
71
|
|
|
71
72
|
## Extend & regenerate
|
|
72
73
|
1) Add mapping fields or provider configs in the spec; include validation and PII paths.
|
|
@@ -94,6 +95,7 @@ var integrationHubDocBlocks = [
|
|
|
94
95
|
## PII & Payloads
|
|
95
96
|
- Treat payload archives as potentially sensitive; mark policy.pii paths.
|
|
96
97
|
- For MCP/web, avoid exposing raw credentials/tokens; store via provider adapters only.
|
|
98
|
+
- Keep MCP endpoint URLs, headers, and tokens in provider connection config, never in mappings.
|
|
97
99
|
|
|
98
100
|
## Verification
|
|
99
101
|
- Include fixtures for mapping changes and sync retries.
|
package/dist/node/index.js
CHANGED
|
@@ -1557,9 +1557,158 @@ function hasChanges(sourceChecksum, targetChecksum) {
|
|
|
1557
1557
|
}
|
|
1558
1558
|
return sourceChecksum !== targetChecksum;
|
|
1559
1559
|
}
|
|
1560
|
+
|
|
1561
|
+
// src/mcp-example.ts
|
|
1562
|
+
import { randomUUID } from "node:crypto";
|
|
1563
|
+
import {
|
|
1564
|
+
createMcpToolsets
|
|
1565
|
+
} from "@contractspec/lib.ai-agent/tools/mcp-client";
|
|
1566
|
+
var DEFAULT_STDIO_ARGS = [
|
|
1567
|
+
"-y",
|
|
1568
|
+
"@modelcontextprotocol/server-filesystem",
|
|
1569
|
+
"."
|
|
1570
|
+
];
|
|
1571
|
+
async function runIntegrationHubMcpExampleFromEnv() {
|
|
1572
|
+
const mode = resolveMode();
|
|
1573
|
+
const config = buildMcpConfigFromEnv();
|
|
1574
|
+
const toolset = await createMcpToolsets([config], {
|
|
1575
|
+
onNameCollision: "error"
|
|
1576
|
+
});
|
|
1577
|
+
try {
|
|
1578
|
+
const toolNames = Object.keys(toolset.tools).sort();
|
|
1579
|
+
const output = {
|
|
1580
|
+
mode,
|
|
1581
|
+
server: {
|
|
1582
|
+
name: config.name,
|
|
1583
|
+
transport: config.transport ?? "stdio"
|
|
1584
|
+
},
|
|
1585
|
+
tools: toolNames
|
|
1586
|
+
};
|
|
1587
|
+
if (mode === "call") {
|
|
1588
|
+
const toolName = requireEnv("CONTRACTSPEC_INTEGRATION_HUB_MCP_TOOL_NAME");
|
|
1589
|
+
const toolArgs = parseRecordEnvOrDefault("CONTRACTSPEC_INTEGRATION_HUB_MCP_TOOL_ARGS_JSON", {});
|
|
1590
|
+
const tool = toolset.tools[toolName];
|
|
1591
|
+
if (!tool?.execute) {
|
|
1592
|
+
throw new Error(`Tool "${toolName}" was not found. Available tools: ${toolNames.join(", ")}`);
|
|
1593
|
+
}
|
|
1594
|
+
const toolOutput = await tool.execute(toolArgs, {
|
|
1595
|
+
toolCallId: `integration-hub-${randomUUID()}`,
|
|
1596
|
+
messages: []
|
|
1597
|
+
});
|
|
1598
|
+
output.toolCall = {
|
|
1599
|
+
name: toolName,
|
|
1600
|
+
args: toolArgs,
|
|
1601
|
+
output: toolOutput
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
return output;
|
|
1605
|
+
} finally {
|
|
1606
|
+
await toolset.cleanup().catch(() => {
|
|
1607
|
+
return;
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
function buildMcpConfigFromEnv() {
|
|
1612
|
+
const name = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_NAME ?? "filesystem";
|
|
1613
|
+
const transport = resolveTransport();
|
|
1614
|
+
const toolPrefix = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_TOOL_PREFIX;
|
|
1615
|
+
if (transport === "stdio") {
|
|
1616
|
+
return {
|
|
1617
|
+
name,
|
|
1618
|
+
transport,
|
|
1619
|
+
command: process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_COMMAND ?? "npx",
|
|
1620
|
+
args: parseStringArrayEnv("CONTRACTSPEC_INTEGRATION_HUB_MCP_ARGS_JSON", DEFAULT_STDIO_ARGS),
|
|
1621
|
+
toolPrefix
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
const accessToken = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_ACCESS_TOKEN;
|
|
1625
|
+
const accessTokenEnvVar = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_ACCESS_TOKEN_ENV;
|
|
1626
|
+
return {
|
|
1627
|
+
name,
|
|
1628
|
+
transport,
|
|
1629
|
+
url: requireEnv("CONTRACTSPEC_INTEGRATION_HUB_MCP_URL"),
|
|
1630
|
+
headers: parseStringRecordEnv("CONTRACTSPEC_INTEGRATION_HUB_MCP_HEADERS_JSON"),
|
|
1631
|
+
accessToken,
|
|
1632
|
+
accessTokenEnvVar,
|
|
1633
|
+
toolPrefix
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function resolveMode() {
|
|
1637
|
+
const rawMode = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_MODE?.toLowerCase() ?? "list";
|
|
1638
|
+
if (rawMode === "list" || rawMode === "call") {
|
|
1639
|
+
return rawMode;
|
|
1640
|
+
}
|
|
1641
|
+
throw new Error(`Unsupported CONTRACTSPEC_INTEGRATION_HUB_MCP_MODE: ${rawMode}. Use "list" or "call".`);
|
|
1642
|
+
}
|
|
1643
|
+
function resolveTransport() {
|
|
1644
|
+
const rawTransport = process.env.CONTRACTSPEC_INTEGRATION_HUB_MCP_TRANSPORT?.toLowerCase() ?? "stdio";
|
|
1645
|
+
if (rawTransport === "stdio" || rawTransport === "http" || rawTransport === "sse") {
|
|
1646
|
+
return rawTransport;
|
|
1647
|
+
}
|
|
1648
|
+
throw new Error(`Unsupported CONTRACTSPEC_INTEGRATION_HUB_MCP_TRANSPORT: ${rawTransport}. Use "stdio", "http", or "sse".`);
|
|
1649
|
+
}
|
|
1650
|
+
function parseStringArrayEnv(key, fallback) {
|
|
1651
|
+
const raw = process.env[key];
|
|
1652
|
+
if (!raw) {
|
|
1653
|
+
return fallback;
|
|
1654
|
+
}
|
|
1655
|
+
const parsed = parseJsonEnv(key);
|
|
1656
|
+
if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== "string")) {
|
|
1657
|
+
throw new Error(`${key} must be a JSON string array.`);
|
|
1658
|
+
}
|
|
1659
|
+
return parsed;
|
|
1660
|
+
}
|
|
1661
|
+
function parseRecordEnv(key) {
|
|
1662
|
+
const raw = process.env[key];
|
|
1663
|
+
if (!raw) {
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
const parsed = parseJsonEnv(key);
|
|
1667
|
+
if (!isRecord(parsed)) {
|
|
1668
|
+
throw new Error(`${key} must be a JSON object.`);
|
|
1669
|
+
}
|
|
1670
|
+
return parsed;
|
|
1671
|
+
}
|
|
1672
|
+
function parseRecordEnvOrDefault(key, fallback) {
|
|
1673
|
+
return parseRecordEnv(key) ?? fallback;
|
|
1674
|
+
}
|
|
1675
|
+
function parseStringRecordEnv(key) {
|
|
1676
|
+
const parsed = parseRecordEnv(key);
|
|
1677
|
+
if (!parsed) {
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
const entries = Object.entries(parsed);
|
|
1681
|
+
const invalidEntry = entries.find(([, value]) => typeof value !== "string");
|
|
1682
|
+
if (invalidEntry) {
|
|
1683
|
+
throw new Error(`${key} must contain only string values.`);
|
|
1684
|
+
}
|
|
1685
|
+
return Object.fromEntries(entries);
|
|
1686
|
+
}
|
|
1687
|
+
function parseJsonEnv(key) {
|
|
1688
|
+
const raw = process.env[key];
|
|
1689
|
+
if (!raw) {
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
try {
|
|
1693
|
+
return JSON.parse(raw);
|
|
1694
|
+
} catch {
|
|
1695
|
+
throw new Error(`${key} contains invalid JSON.`);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
function requireEnv(key) {
|
|
1699
|
+
const value = process.env[key];
|
|
1700
|
+
if (!value) {
|
|
1701
|
+
throw new Error(`Missing required env var: ${key}`);
|
|
1702
|
+
}
|
|
1703
|
+
return value;
|
|
1704
|
+
}
|
|
1705
|
+
function isRecord(value) {
|
|
1706
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1707
|
+
}
|
|
1560
1708
|
export {
|
|
1561
1709
|
useIntegrationData,
|
|
1562
1710
|
syncConfigMarkdownRenderer,
|
|
1711
|
+
runIntegrationHubMcpExampleFromEnv,
|
|
1563
1712
|
integrationDashboardMarkdownRenderer,
|
|
1564
1713
|
hasChanges,
|
|
1565
1714
|
createSyncEngine,
|