dexto 1.13.4 → 1.14.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 (62) hide show
  1. package/dist/api/mcp/http-gateway.d.ts +21 -0
  2. package/dist/api/mcp/http-gateway.d.ts.map +1 -0
  3. package/dist/api/mcp/http-gateway.js +110 -0
  4. package/dist/api/mcp/tool-aggregation-handler.d.ts +9 -6
  5. package/dist/api/mcp/tool-aggregation-handler.d.ts.map +1 -1
  6. package/dist/api/mcp/tool-aggregation-handler.js +203 -70
  7. package/dist/api/server-hono.d.ts +2 -0
  8. package/dist/api/server-hono.d.ts.map +1 -1
  9. package/dist/api/server-hono.js +377 -276
  10. package/dist/cli/approval/headless-approval-handler.d.ts +4 -0
  11. package/dist/cli/approval/headless-approval-handler.d.ts.map +1 -0
  12. package/dist/cli/approval/headless-approval-handler.js +19 -0
  13. package/dist/cli/auth/api-client.js +1 -1
  14. package/dist/cli/auth/constants.d.ts +1 -0
  15. package/dist/cli/auth/constants.d.ts.map +1 -1
  16. package/dist/cli/auth/constants.js +2 -1
  17. package/dist/cli/auth/dexto-api-key.d.ts.map +1 -1
  18. package/dist/cli/auth/dexto-api-key.js +3 -0
  19. package/dist/cli/auth/login-persistence.d.ts +1 -1
  20. package/dist/cli/auth/login-persistence.d.ts.map +1 -1
  21. package/dist/cli/auth/login-persistence.js +9 -1
  22. package/dist/cli/auth/service.d.ts +2 -0
  23. package/dist/cli/auth/service.d.ts.map +1 -1
  24. package/dist/cli/auth/service.js +1 -0
  25. package/dist/cli/commands/agents/register.d.ts.map +1 -1
  26. package/dist/cli/commands/agents/register.js +0 -1
  27. package/dist/cli/commands/auth/login.d.ts.map +1 -1
  28. package/dist/cli/commands/auth/login.js +9 -4
  29. package/dist/cli/commands/cloud/discovery.d.ts +17 -0
  30. package/dist/cli/commands/cloud/discovery.d.ts.map +1 -0
  31. package/dist/cli/commands/cloud/discovery.js +88 -0
  32. package/dist/cli/commands/cloud/register.d.ts +3 -0
  33. package/dist/cli/commands/cloud/register.d.ts.map +1 -0
  34. package/dist/cli/commands/cloud/register.js +27 -0
  35. package/dist/cli/commands/mcp/register.d.ts +1 -1
  36. package/dist/cli/commands/mcp/register.d.ts.map +1 -1
  37. package/dist/cli/commands/mcp/register.js +259 -26
  38. package/dist/cli/commands/mcp/standalone.d.ts +98 -0
  39. package/dist/cli/commands/mcp/standalone.d.ts.map +1 -0
  40. package/dist/cli/commands/mcp/standalone.js +489 -0
  41. package/dist/cli/commands/run/headless.d.ts +3 -1
  42. package/dist/cli/commands/run/headless.d.ts.map +1 -1
  43. package/dist/cli/commands/run/headless.js +13 -1
  44. package/dist/cli/commands/run/output.d.ts +21 -0
  45. package/dist/cli/commands/run/output.d.ts.map +1 -0
  46. package/dist/cli/commands/run/output.js +95 -0
  47. package/dist/cli/commands/run/register.d.ts.map +1 -1
  48. package/dist/cli/commands/run/register.js +27 -3
  49. package/dist/cli/commands/setup.d.ts +2 -2
  50. package/dist/cli/commands/trace/client.d.ts +2 -2
  51. package/dist/cli/utils/options.js +3 -1
  52. package/dist/config/cli-overrides.d.ts +3 -1
  53. package/dist/config/cli-overrides.d.ts.map +1 -1
  54. package/dist/config/cli-overrides.js +9 -3
  55. package/dist/index-main.js +11 -10
  56. package/dist/utils/env.d.ts +2 -0
  57. package/dist/utils/env.d.ts.map +1 -1
  58. package/dist/utils/env.js +6 -0
  59. package/dist/utils/graceful-shutdown.d.ts +1 -1
  60. package/dist/utils/graceful-shutdown.d.ts.map +1 -1
  61. package/dist/utils/graceful-shutdown.js +28 -9
  62. package/package.json +14 -14
@@ -0,0 +1,21 @@
1
+ import { type IncomingHttpHeaders } from 'node:http';
2
+ import type { ValidatedServersConfig } from '@dexto/core/mcp';
3
+ import type { HttpAuthorization } from '@dexto/server/hono/auth';
4
+ export interface McpHttpAggregationGateway {
5
+ port: number;
6
+ close(): Promise<void>;
7
+ }
8
+ interface HttpAggregationOptions {
9
+ name: string;
10
+ version: string;
11
+ strict: boolean;
12
+ host: string;
13
+ port: number;
14
+ authorize: HttpAuthorization;
15
+ }
16
+ /** HTTP authority checks protect open loopback listeners against browser rebinding. */
17
+ export declare function allowsMcpHttpRequestOrigin(host: string, port: number, headers: IncomingHttpHeaders): boolean;
18
+ /** Stateless MCP HTTP requests share one host-owned upstream discovery snapshot. */
19
+ export declare function startMcpHttpAggregationGateway(configs: ValidatedServersConfig, options: HttpAggregationOptions): Promise<McpHttpAggregationGateway>;
20
+ export {};
21
+ //# sourceMappingURL=http-gateway.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-gateway.d.ts","sourceRoot":"","sources":["../../../src/api/mcp/http-gateway.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAGnE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAGjE,MAAM,WAAW,yBAAyB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,UAAU,sBAAsB;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,iBAAiB,CAAC;CAChC;AAED,uFAAuF;AACvF,wBAAgB,0BAA0B,CACtC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,mBAAmB,GAC7B,OAAO,CAQT;AAED,oFAAoF;AACpF,wBAAsB,8BAA8B,CAChD,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,sBAAsB,GAChC,OAAO,CAAC,yBAAyB,CAAC,CAqGpC"}
@@ -0,0 +1,110 @@
1
+ import { isIP } from 'node:net';
2
+ import { createServer } from 'node:http';
3
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
+ import { initializeMcpToolAggregationHost } from './tool-aggregation-handler.js';
5
+ /** HTTP authority checks protect open loopback listeners against browser rebinding. */
6
+ export function allowsMcpHttpRequestOrigin(host, port, headers) {
7
+ const listenerHost = isIP(host) === 6 ? `[${host}]` : host;
8
+ const listener = new URL(`http://${listenerHost}:${port}`);
9
+ const loopback = host === '127.0.0.1' || host === '::1';
10
+ return ((!loopback || headers.host === listener.host) &&
11
+ (headers.origin === undefined || headers.origin === listener.origin));
12
+ }
13
+ /** Stateless MCP HTTP requests share one host-owned upstream discovery snapshot. */
14
+ export async function startMcpHttpAggregationGateway(configs, options) {
15
+ const owner = await initializeMcpToolAggregationHost(configs, options.name, options.version, options.strict);
16
+ const server = createServer((request, response) => {
17
+ // Authorization precedes routing, body parsing and downstream protocol allocation.
18
+ const authorization = options.authorize(request.headers.authorization);
19
+ if (!authorization.allowed) {
20
+ response.writeHead(authorization.status, { 'content-type': 'application/json' });
21
+ response.end(JSON.stringify(authorization.body));
22
+ return;
23
+ }
24
+ const address = server.address();
25
+ if (address === null || typeof address === 'string') {
26
+ response.writeHead(503).end();
27
+ return;
28
+ }
29
+ if (!allowsMcpHttpRequestOrigin(options.host, address.port, request.headers)) {
30
+ response.writeHead(403).end();
31
+ return;
32
+ }
33
+ if (request.url?.split('?')[0] !== '/mcp') {
34
+ response.writeHead(404).end();
35
+ return;
36
+ }
37
+ if (request.method !== 'POST') {
38
+ response.writeHead(405, { allow: 'POST' }).end();
39
+ return;
40
+ }
41
+ // SDK stateless transports cannot be reused, even by a single HTTP client.
42
+ const transport = new StreamableHTTPServerTransport({
43
+ enableJsonResponse: true,
44
+ });
45
+ // The SDK class implements Transport, but its callback getters include undefined,
46
+ // conflicting with Transport's optional properties under exactOptionalPropertyTypes.
47
+ const peer = owner.connect(transport);
48
+ let closePromise;
49
+ const closeRequest = () => {
50
+ response.off('close', closeRequest);
51
+ response.off('finish', closeRequest);
52
+ if (closePromise !== undefined)
53
+ return;
54
+ closePromise = peer.then((connection) => connection.close());
55
+ void closePromise.catch(() => {
56
+ process.stderr.write('MCP HTTP request cleanup failed.\n');
57
+ });
58
+ };
59
+ response.once('close', closeRequest);
60
+ response.once('finish', closeRequest);
61
+ void peer
62
+ .then(() => transport.handleRequest(request, response))
63
+ .catch(() => {
64
+ if (!response.headersSent)
65
+ response.writeHead(500);
66
+ response.end();
67
+ closeRequest();
68
+ });
69
+ });
70
+ let closePromise;
71
+ const close = () => {
72
+ closePromise ??= Promise.resolve().then(async () => {
73
+ const listenerClosed = server.listening
74
+ ? new Promise((resolve, reject) => {
75
+ server.close((error) => (error ? reject(error) : resolve()));
76
+ server.closeAllConnections();
77
+ })
78
+ : Promise.resolve();
79
+ const results = await Promise.allSettled([listenerClosed, owner.close()]);
80
+ const failures = results.filter((result) => result.status === 'rejected');
81
+ if (failures.length > 0) {
82
+ throw new AggregateError(failures.map((result) => result.reason), 'MCP HTTP gateway cleanup failed');
83
+ }
84
+ });
85
+ return closePromise;
86
+ };
87
+ try {
88
+ await new Promise((resolve, reject) => {
89
+ const onError = (error) => {
90
+ server.off('listening', onListening);
91
+ reject(error);
92
+ };
93
+ const onListening = () => {
94
+ server.off('error', onError);
95
+ resolve();
96
+ };
97
+ server.once('error', onError);
98
+ server.once('listening', onListening);
99
+ server.listen(options.port, options.host);
100
+ });
101
+ const address = server.address();
102
+ if (address === null || typeof address === 'string')
103
+ throw new Error('MCP HTTP listener address unavailable');
104
+ return { port: address.port, close };
105
+ }
106
+ catch (error) {
107
+ await close().catch(() => undefined);
108
+ throw error;
109
+ }
110
+ }
@@ -1,9 +1,12 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
3
- import { type ValidatedServersConfig } from '@dexto/core';
4
- /**
5
- * Initializes MCP server for tool aggregation mode.
6
- * Instead of exposing an AI agent, this directly exposes all tools from connected MCP servers.
7
- */
8
- export declare function initializeMcpToolAggregationServer(serverConfigs: ValidatedServersConfig, mcpTransport: Transport, serverName: string, serverVersion: string, _strict: boolean): Promise<McpServer>;
3
+ import { type ValidatedServersConfig } from '@dexto/core/mcp';
4
+ export interface McpToolAggregationHost {
5
+ connect(transport: Transport): Promise<McpServer>;
6
+ close(): Promise<void>;
7
+ }
8
+ /** One upstream discovery snapshot shared by independently connected downstream peers. */
9
+ export declare function initializeMcpToolAggregationHost(serverConfigs: ValidatedServersConfig, serverName: string, serverVersion: string, strict: boolean): Promise<McpToolAggregationHost>;
10
+ /** Stdio-compatible owner: closing its downstream peer also closes its upstream snapshot. */
11
+ export declare function initializeMcpToolAggregationServer(serverConfigs: ValidatedServersConfig, mcpTransport: Transport, serverName: string, serverVersion: string, strict: boolean): Promise<McpServer>;
9
12
  //# sourceMappingURL=tool-aggregation-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool-aggregation-handler.d.ts","sourceRoot":"","sources":["../../../src/api/mcp/tool-aggregation-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAC/E,OAAO,EAGH,KAAK,sBAAsB,EAI9B,MAAM,aAAa,CAAC;AAGrB;;;GAGG;AACH,wBAAsB,kCAAkC,CACpD,aAAa,EAAE,sBAAsB,EACrC,YAAY,EAAE,SAAS,EACvB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,OAAO,GACjB,OAAO,CAAC,SAAS,CAAC,CAwGpB"}
1
+ {"version":3,"file":"tool-aggregation-handler.d.ts","sourceRoot":"","sources":["../../../src/api/mcp/tool-aggregation-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAC/E,OAAO,EAA4B,KAAK,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAiBxF,MAAM,WAAW,sBAAsB;IACnC,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAClD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,0FAA0F;AAC1F,wBAAsB,gCAAgC,CAClD,aAAa,EAAE,sBAAsB,EACrC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,OAAO,GAChB,OAAO,CAAC,sBAAsB,CAAC,CA8PjC;AAED,6FAA6F;AAC7F,wBAAsB,kCAAkC,CACpD,aAAa,EAAE,sBAAsB,EACrC,YAAY,EAAE,SAAS,EACvB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,OAAO,GAChB,OAAO,CAAC,SAAS,CAAC,CAuBpB"}
@@ -1,92 +1,225 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { MCPManager, logger, jsonSchemaToZodShape, createLogger, DextoLogComponent, } from '@dexto/core';
3
- import { z } from 'zod';
4
- /**
5
- * Initializes MCP server for tool aggregation mode.
6
- * Instead of exposing an AI agent, this directly exposes all tools from connected MCP servers.
7
- */
8
- export async function initializeMcpToolAggregationServer(serverConfigs, mcpTransport, serverName, serverVersion, _strict) {
9
- // Create MCP manager with no confirmation provider (tools are auto-approved)
2
+ import { MCPManager, MCPErrorCode } from '@dexto/core/mcp';
3
+ import { createLogger, DextoLogComponent } from '@dexto/core/logger';
4
+ import { ListToolsRequestSchema, CallToolRequestSchema, McpError, ErrorCode, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ /** One upstream discovery snapshot shared by independently connected downstream peers. */
6
+ export async function initializeMcpToolAggregationHost(serverConfigs, serverName, serverVersion, strict) {
7
+ // Manager diagnostics can contain upstream payloads; keep them off protocol streams.
10
8
  const mcpLogger = createLogger({
11
9
  config: {
12
10
  level: 'info',
13
- transports: [{ type: 'console', colorize: true }],
11
+ transports: [{ type: 'silent' }],
14
12
  },
15
13
  agentId: 'mcp-tool-aggregation',
16
14
  component: DextoLogComponent.MCP,
17
15
  });
18
16
  const mcpManager = new MCPManager(mcpLogger);
19
- // Initialize all MCP server connections from config
20
- logger.info('Connecting to configured MCP servers for tool aggregation...');
21
- await mcpManager.initializeFromConfig(serverConfigs);
22
- // Create the aggregation MCP server
23
- const mcpServer = new McpServer({ name: serverName, version: serverVersion }, {
24
- capabilities: {
25
- tools: {},
26
- resources: {},
27
- prompts: {},
28
- },
29
- });
30
- const toolDefinitions = await mcpManager.getAllTools();
31
- let toolCount = 0;
32
- for (const [toolName, toolDef] of Object.entries(toolDefinitions)) {
33
- toolCount++;
34
- const jsonSchema = toolDef.parameters ?? { type: 'object', properties: {} };
35
- const paramsShape = jsonSchemaToZodShape(jsonSchema);
36
- const _paramsSchema = z.object(paramsShape);
37
- logger.debug(`Registering tool '${toolName}' with schema: ${JSON.stringify(jsonSchema)}`);
38
- mcpServer.tool(toolName, toolDef.description || `Tool: ${toolName}`, paramsShape, async (args) => {
39
- logger.info(`Tool aggregation: executing ${toolName}`);
40
- try {
41
- const result = await mcpManager.executeTool(toolName, args);
42
- logger.info(`Tool aggregation: ${toolName} completed successfully`);
43
- return result;
44
- }
45
- catch (error) {
46
- logger.error(`Tool aggregation: ${toolName} failed: ${error}`);
47
- throw error;
17
+ const peers = new Map();
18
+ const attaching = new Set();
19
+ let closePromise;
20
+ const close = () => {
21
+ closePromise ??= Promise.resolve().then(async () => {
22
+ await Promise.allSettled(Array.from(attaching));
23
+ const results = await Promise.allSettled(Array.from(peers.values(), (closePeer) => closePeer()));
24
+ results.push(...(await Promise.allSettled([mcpManager.disconnectAll()])));
25
+ results.push(...(await Promise.allSettled([mcpLogger.destroy()])));
26
+ const failures = results.filter((result) => result.status === 'rejected');
27
+ if (failures.length > 0) {
28
+ throw new AggregateError(failures.map((result) => result.reason), 'MCP aggregation cleanup failed');
48
29
  }
49
30
  });
31
+ return closePromise;
32
+ };
33
+ const connectionConfigs = { ...serverConfigs };
34
+ if (strict) {
35
+ for (const [name, config] of Object.entries(serverConfigs)) {
36
+ if (config.enabled)
37
+ connectionConfigs[name] = { ...config, connectionMode: 'strict' };
38
+ }
50
39
  }
51
- logger.info(`Registered ${toolCount} tools from connected MCP servers`);
52
- // Register resources if available
53
40
  try {
54
- const allResources = await mcpManager.listAllResources();
55
- logger.info(`Registering ${allResources.length} resources from connected MCP servers`);
56
- // Collision handling verified:
57
- // - Tools/Prompts: Names come from mcpManager which handles collisions at source
58
- // - Resources: Index prefix ensures uniqueness even if multiple clients have same key
59
- allResources.forEach((resource, index) => {
60
- const safeId = resource.key.replace(/[^a-zA-Z0-9]/g, '_');
61
- mcpServer.resource(`resource_${index}_${safeId}`, resource.key, async () => {
62
- logger.info(`Resource aggregation: reading ${resource.key}`);
63
- return await mcpManager.readResource(resource.key);
41
+ await mcpManager.initializeFromConfig(connectionConfigs);
42
+ if (Object.values(mcpManager.getFailedConnections()).some((error) => error.code === MCPErrorCode.DUPLICATE_NAME)) {
43
+ throw new Error('Duplicate upstream server identity');
44
+ }
45
+ const tools = new Map();
46
+ const discovered = new Map();
47
+ await Promise.all(Array.from(mcpManager.getClients(), async ([name, connection]) => {
48
+ const config = serverConfigs[name];
49
+ if (config === undefined)
50
+ throw new Error('Missing upstream connection configuration');
51
+ const client = await connection.getConnectedClient();
52
+ const capabilities = client.getServerCapabilities();
53
+ const options = { timeout: config.timeout };
54
+ const [toolResult, resourceResult, promptResult] = await Promise.all([
55
+ capabilities?.tools ? client.listTools(undefined, options) : { tools: [] },
56
+ capabilities?.resources
57
+ ? client.listResources(undefined, options)
58
+ : { resources: [] },
59
+ capabilities?.prompts
60
+ ? client.listPrompts(undefined, options)
61
+ : { prompts: [] },
62
+ ]);
63
+ const upstreamTools = new Map(toolResult.tools.map((tool) => [tool.name, tool]));
64
+ if (upstreamTools.size !== toolResult.tools.length)
65
+ throw new Error('Duplicate upstream tool identity');
66
+ const upstreamResources = new Map(resourceResult.resources.map((resource) => [resource.uri, resource]));
67
+ if (upstreamResources.size !== resourceResult.resources.length)
68
+ throw new Error('Duplicate upstream resource identity');
69
+ discovered.set(name, {
70
+ client,
71
+ tools: upstreamTools,
72
+ resources: upstreamResources,
73
+ prompts: promptResult.prompts,
74
+ timeout: config.timeout,
64
75
  });
65
- });
76
+ }));
77
+ let discoveredCount = 0;
78
+ for (const upstream of discovered.values())
79
+ discoveredCount += upstream.tools.size;
80
+ const aliases = mcpManager.getAllToolsWithServerInfo();
81
+ if (aliases.size !== discoveredCount)
82
+ throw new Error('Ambiguous aggregation tool aliases');
83
+ for (const [alias, entry] of aliases) {
84
+ const upstream = discovered.get(entry.serverName);
85
+ const definition = upstream?.tools.get(entry.upstreamToolName);
86
+ if (upstream === undefined || definition === undefined) {
87
+ throw new Error('Aggregation tool identity changed during startup');
88
+ }
89
+ tools.set(alias, {
90
+ definition: { ...definition, name: alias },
91
+ client: upstream.client,
92
+ name: entry.upstreamToolName,
93
+ timeout: upstream.timeout,
94
+ });
95
+ }
96
+ const resources = new Map();
97
+ const cachedResources = await mcpManager.listAllResources();
98
+ let discoveredResourceCount = 0;
99
+ for (const upstream of discovered.values())
100
+ discoveredResourceCount += upstream.resources.size;
101
+ if (cachedResources.length !== discoveredResourceCount)
102
+ throw new Error('Ambiguous aggregation resource identities');
103
+ for (const resource of cachedResources) {
104
+ const upstream = discovered.get(resource.serverName);
105
+ const definition = upstream?.resources.get(resource.summary.uri);
106
+ if (upstream === undefined || definition === undefined)
107
+ throw new Error('Aggregation resource identity changed during startup');
108
+ resources.set(resource.key, {
109
+ definition: { ...definition, uri: resource.key },
110
+ client: upstream.client,
111
+ uri: definition.uri,
112
+ timeout: upstream.timeout,
113
+ });
114
+ }
115
+ const prompts = new Map();
116
+ for (const upstream of discovered.values()) {
117
+ for (const prompt of upstream.prompts) {
118
+ if (prompts.has(prompt.name))
119
+ throw new Error('Duplicate aggregation prompt identity');
120
+ prompts.set(prompt.name, {
121
+ definition: prompt,
122
+ client: upstream.client,
123
+ timeout: upstream.timeout,
124
+ });
125
+ }
126
+ }
127
+ return {
128
+ close,
129
+ async connect(transport) {
130
+ if (closePromise !== undefined)
131
+ throw new Error('MCP aggregation host is closing');
132
+ const attachment = (async () => {
133
+ const mcpServer = new McpServer({ name: serverName, version: serverVersion }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
134
+ const originalClose = mcpServer.close.bind(mcpServer);
135
+ let peerClosePromise;
136
+ const closePeer = () => (peerClosePromise ??= originalClose());
137
+ mcpServer.close = closePeer;
138
+ peers.set(mcpServer, closePeer);
139
+ const previousClose = mcpServer.server.onclose;
140
+ mcpServer.server.onclose = () => {
141
+ peers.delete(mcpServer);
142
+ previousClose?.();
143
+ };
144
+ mcpServer.server.setRequestHandler(ListToolsRequestSchema, async () => ({
145
+ tools: Array.from(tools.values(), (tool) => tool.definition),
146
+ }));
147
+ mcpServer.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
148
+ const tool = tools.get(request.params.name);
149
+ if (tool === undefined)
150
+ throw new McpError(ErrorCode.InvalidParams, 'Unknown aggregation tool');
151
+ return tool.client.callTool({ ...request.params, name: tool.name }, undefined, {
152
+ signal: extra.signal,
153
+ timeout: tool.timeout,
154
+ });
155
+ });
156
+ mcpServer.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
157
+ resources: Array.from(resources.values(), (resource) => resource.definition),
158
+ }));
159
+ mcpServer.server.setRequestHandler(ReadResourceRequestSchema, async (request, extra) => {
160
+ const resource = resources.get(request.params.uri);
161
+ if (resource === undefined)
162
+ throw new McpError(ErrorCode.InvalidParams, 'Unknown aggregation resource');
163
+ return resource.client.readResource({ ...request.params, uri: resource.uri }, { signal: extra.signal, timeout: resource.timeout });
164
+ });
165
+ mcpServer.server.setRequestHandler(ListPromptsRequestSchema, async () => ({
166
+ prompts: Array.from(prompts.values(), (prompt) => prompt.definition),
167
+ }));
168
+ mcpServer.server.setRequestHandler(GetPromptRequestSchema, async (request, extra) => {
169
+ const prompt = prompts.get(request.params.name);
170
+ if (prompt === undefined)
171
+ throw new McpError(ErrorCode.InvalidParams, 'Unknown aggregation prompt');
172
+ return prompt.client.getPrompt(request.params, {
173
+ signal: extra.signal,
174
+ timeout: prompt.timeout,
175
+ });
176
+ });
177
+ try {
178
+ await mcpServer.connect(transport);
179
+ if (closePromise !== undefined)
180
+ throw new Error('MCP aggregation host is closing');
181
+ return mcpServer;
182
+ }
183
+ catch (error) {
184
+ await closePeer().catch(() => undefined);
185
+ peers.delete(mcpServer);
186
+ throw error;
187
+ }
188
+ })();
189
+ attaching.add(attachment);
190
+ try {
191
+ return await attachment;
192
+ }
193
+ finally {
194
+ attaching.delete(attachment);
195
+ }
196
+ },
197
+ };
66
198
  }
67
199
  catch (error) {
68
- logger.debug(`Skipping resource aggregation: ${error}`);
200
+ await close().catch(() => undefined);
201
+ throw error;
69
202
  }
70
- // Register prompts if available
203
+ }
204
+ /** Stdio-compatible owner: closing its downstream peer also closes its upstream snapshot. */
205
+ export async function initializeMcpToolAggregationServer(serverConfigs, mcpTransport, serverName, serverVersion, strict) {
206
+ const host = await initializeMcpToolAggregationHost(serverConfigs, serverName, serverVersion, strict);
71
207
  try {
72
- const allPrompts = await mcpManager.listAllPrompts();
73
- logger.info(`Registering ${allPrompts.length} prompts from connected MCP servers`);
74
- for (const promptName of allPrompts) {
75
- mcpServer.prompt(promptName, `Prompt: ${promptName}`, async (extra) => {
76
- logger.info(`Prompt aggregation: resolving ${promptName}`);
77
- const promptArgs = extra && 'arguments' in extra
78
- ? extra.arguments
79
- : undefined;
80
- return await mcpManager.getPrompt(promptName, promptArgs);
81
- });
82
- }
208
+ const server = await host.connect(mcpTransport);
209
+ server.close = host.close;
210
+ const previousClose = server.server.onclose;
211
+ server.server.onclose = () => {
212
+ try {
213
+ previousClose?.();
214
+ }
215
+ finally {
216
+ void host.close().catch(() => undefined);
217
+ }
218
+ };
219
+ return server;
83
220
  }
84
221
  catch (error) {
85
- logger.debug(`Skipping prompt aggregation: ${error}`);
222
+ await host.close().catch(() => undefined);
223
+ throw error;
86
224
  }
87
- // Connect server to transport
88
- logger.info(`Connecting MCP tool aggregation server...`);
89
- await mcpServer.connect(mcpTransport);
90
- logger.info(`✅ MCP tool aggregation server connected with ${toolCount} tools exposed`);
91
- return mcpServer;
92
225
  }
@@ -3,6 +3,7 @@ import { DextoAgent } from '@dexto/core';
3
3
  import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
4
4
  import { createDextoApp, createNodeServer, type WebUIRuntimeConfig } from '@dexto/server';
5
5
  export type HonoInitializationResult = {
6
+ stop: () => Promise<void>;
6
7
  app: ReturnType<typeof createDextoApp>;
7
8
  server: ReturnType<typeof createNodeServer>['server'];
8
9
  webhookSubscriber?: NonNullable<ReturnType<typeof createNodeServer>['webhookSubscriber']>;
@@ -26,6 +27,7 @@ export type HonoInitializationResult = {
26
27
  export declare function initializeHonoApi(agent: DextoAgent, agentCardOverride?: Partial<AgentCard>, listenPort?: number, agentId?: string, configFilePath?: string, workspaceRoot?: string, webRoot?: string, webUIConfig?: WebUIRuntimeConfig): Promise<HonoInitializationResult>;
27
28
  export declare function startHonoApiServer(agent: DextoAgent, port?: number, agentCardOverride?: Partial<AgentCard>, agentId?: string, configFilePath?: string, workspaceRoot?: string, webRoot?: string, webUIConfig?: WebUIRuntimeConfig): Promise<{
28
29
  server: ReturnType<typeof createNodeServer>['server'];
30
+ stop: () => Promise<void>;
29
31
  webhookSubscriber?: NonNullable<ReturnType<typeof createNodeServer>['webhookSubscriber']>;
30
32
  }>;
31
33
  //# sourceMappingURL=server-hono.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server-hono.d.ts","sourceRoot":"","sources":["../../src/api/server-hono.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAuC,MAAM,aAAa,CAAC;AAY9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAC/E,OAAO,EACH,cAAc,EACd,gBAAgB,EAWhB,KAAK,kBAAkB,EAC1B,MAAM,eAAe,CAAC;AAmGvB,MAAM,MAAM,wBAAwB,GAAG;IACnC,GAAG,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;IACvC,MAAM,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,QAAQ,CAAC,CAAC;IACtD,iBAAiB,CAAC,EAAE,WAAW,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAC1F,SAAS,EAAE,SAAS,CAAC;IACrB,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,iBAAiB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/E,gBAAgB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7E,oBAAoB,EAAE,MAAM,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;CAC9C,CAAC;AAGF,wBAAsB,iBAAiB,CACnC,KAAK,EAAE,UAAU,EACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,EACtC,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,EAChB,cAAc,CAAC,EAAE,MAAM,EACvB,aAAa,CAAC,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,kBAAkB,GACjC,OAAO,CAAC,wBAAwB,CAAC,CAiYnC;AAED,wBAAsB,kBAAkB,CACpC,KAAK,EAAE,UAAU,EACjB,IAAI,SAAO,EACX,iBAAiB,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,EACtC,OAAO,CAAC,EAAE,MAAM,EAChB,cAAc,CAAC,EAAE,MAAM,EACvB,aAAa,CAAC,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,kBAAkB,GACjC,OAAO,CAAC;IACP,MAAM,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,QAAQ,CAAC,CAAC;IACtD,iBAAiB,CAAC,EAAE,WAAW,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC;CAC7F,CAAC,CAkCD"}
1
+ {"version":3,"file":"server-hono.d.ts","sourceRoot":"","sources":["../../src/api/server-hono.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAuC,MAAM,aAAa,CAAC;AAY9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAC/E,OAAO,EACH,cAAc,EACd,gBAAgB,EAWhB,KAAK,kBAAkB,EAC1B,MAAM,eAAe,CAAC;AAmGvB,MAAM,MAAM,wBAAwB,GAAG;IACnC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,GAAG,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;IACvC,MAAM,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,QAAQ,CAAC,CAAC;IACtD,iBAAiB,CAAC,EAAE,WAAW,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAC1F,SAAS,EAAE,SAAS,CAAC;IACrB,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,iBAAiB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/E,gBAAgB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7E,oBAAoB,EAAE,MAAM,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;CAC9C,CAAC;AAGF,wBAAsB,iBAAiB,CACnC,KAAK,EAAE,UAAU,EACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,EACtC,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,EAChB,cAAc,CAAC,EAAE,MAAM,EACvB,aAAa,CAAC,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,kBAAkB,GACjC,OAAO,CAAC,wBAAwB,CAAC,CAsdnC;AAED,wBAAsB,kBAAkB,CACpC,KAAK,EAAE,UAAU,EACjB,IAAI,SAAO,EACX,iBAAiB,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,EACtC,OAAO,CAAC,EAAE,MAAM,EAChB,cAAc,CAAC,EAAE,MAAM,EACvB,aAAa,CAAC,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,kBAAkB,GACjC,OAAO,CAAC;IACP,MAAM,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,QAAQ,CAAC,CAAC;IACtD,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,iBAAiB,CAAC,EAAE,WAAW,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC;CAC7F,CAAC,CAkDD"}