@contractspec/example.integration-hub 2.6.0 → 2.7.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.
Files changed (48) hide show
  1. package/README.md +40 -0
  2. package/dist/docs/index.js +2 -0
  3. package/dist/docs/integration-hub.docblock.js +2 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +149 -0
  6. package/dist/mcp-example.d.ts +16 -0
  7. package/dist/mcp-example.js +151 -0
  8. package/dist/node/docs/index.js +2 -0
  9. package/dist/node/docs/integration-hub.docblock.js +2 -0
  10. package/dist/node/index.js +149 -0
  11. package/dist/node/mcp-example.js +150 -0
  12. package/dist/node/run-mcp.js +155 -0
  13. package/dist/run-mcp.d.ts +1 -0
  14. package/dist/run-mcp.js +156 -0
  15. package/package.json +34 -94
  16. package/dist/browser/connection/connection.enum.js +0 -12
  17. package/dist/browser/connection/connection.operation.js +0 -101
  18. package/dist/browser/connection/connection.presentation.js +0 -102
  19. package/dist/browser/connection/connection.schema.js +0 -48
  20. package/dist/browser/connection/index.js +0 -104
  21. package/dist/browser/docs/index.js +0 -104
  22. package/dist/browser/docs/integration-hub.docblock.js +0 -104
  23. package/dist/browser/events.js +0 -211
  24. package/dist/browser/example.js +0 -42
  25. package/dist/browser/handlers/index.js +0 -246
  26. package/dist/browser/handlers/integration.handlers.js +0 -246
  27. package/dist/browser/index.js +0 -1595
  28. package/dist/browser/integration/index.js +0 -92
  29. package/dist/browser/integration/integration.enum.js +0 -12
  30. package/dist/browser/integration/integration.operations.js +0 -89
  31. package/dist/browser/integration/integration.presentation.js +0 -120
  32. package/dist/browser/integration/integration.schema.js +0 -42
  33. package/dist/browser/integration-hub.capability.js +0 -43
  34. package/dist/browser/integration-hub.feature.js +0 -114
  35. package/dist/browser/seeders/index.js +0 -60
  36. package/dist/browser/sync/index.js +0 -332
  37. package/dist/browser/sync/sync.enum.js +0 -26
  38. package/dist/browser/sync/sync.operations.js +0 -321
  39. package/dist/browser/sync/sync.presentation.js +0 -301
  40. package/dist/browser/sync/sync.schema.js +0 -154
  41. package/dist/browser/sync-engine/index.js +0 -186
  42. package/dist/browser/tests/operations.test-spec.js +0 -85
  43. package/dist/browser/ui/IntegrationDashboard.js +0 -369
  44. package/dist/browser/ui/hooks/index.js +0 -57
  45. package/dist/browser/ui/hooks/useIntegrationData.js +0 -54
  46. package/dist/browser/ui/index.js +0 -644
  47. package/dist/browser/ui/renderers/index.js +0 -273
  48. 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
@@ -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
@@ -9,3 +9,4 @@ export * from './sync';
9
9
  export { createIntegrationHandlers, type IntegrationHandlers, } from './handlers/integration.handlers';
10
10
  export * from './ui';
11
11
  export * from './sync-engine';
12
+ export * from './mcp-example';
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
+ };
@@ -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.
@@ -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,