2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
import { CancellationToken } from '../../../../base/common/cancellation.js';
7
>
import { Emitter, Event } from '../../../../base/common/event.js';
8
>
import { Disposable } from '../../../../base/common/lifecycle.js';
9
>
import { autorun } from '../../../../base/common/observable.js';
10
>
import { IServerChannel } from '../../../../base/parts/ipc/common/ipc.js';
11
>
import { ILogService } from '../../../../platform/log/common/log.js';
12
>
import { IMcpGatewayServerDescriptor } from '../../../../platform/mcp/common/mcpGateway.js';
13
>
import { MCP } from '../../../../platform/mcp/common/modelContextProtocol.js';
14
>
import { URI } from '../../../../base/common/uri.js';
15
>
import { McpServer } from './mcpServer.js';
16
>
import { IMcpServer, IMcpService, McpCapability, McpServerCacheState, McpToolVisibility } from './mcpTypes.js';
17
>
import { startServerAndWaitForLiveTools } from './mcpTypesUtils.js';
18
>
19
>
interface ICallToolForServerArgs {
20
>
serverId: string;
21
>
name: string;
22
>
args: Record<string, unknown>;
23
>
chatSessionResource?: string;
24
>
}
25
>
26
>
interface IReadResourceForServerArgs {
27
>
serverId: string;
28
>
uri: string;
29
>
}
30
>
31
>
interface IServerIdArg {
32
>
serverId: string;
33
>
}
34
>
35
>
export class McpGatewayToolBrokerChannel extends Disposable implements IServerChannel<unknown> {
36
>
private readonly _onDidChangeTools = this._register(new Emitter<void>());
37
>
private readonly _onDidChangeResources = this._register(new Emitter<void>());
38
>
private readonly _onDidChangeServers = this._register(new Emitter<readonly IMcpGatewayServerDescriptor[]>());
39
>
40
>
/**
41
>
* Per-server promise that races server startup against the grace period timeout.
42
>
* Once set for a server, subsequent list calls await the already-resolved promise
43
>
* and return immediately instead of waiting again.
44
>
*
45
>
* The `resolved` flag tracks whether the promise has settled. If a server's
46
>
* cacheState regresses to Unknown/Outdated after the promise resolved (e.g.
47
>
* after a cache reset), `_waitForStartup` discards the stale entry and creates
48
>
* a fresh race so the server gets another chance to start.
49
>
*/
50
>
private readonly _startupGrace = new Map<string, { promise: Promise<boolean>; resolved: boolean }>();
51
>
52
>
constructor(
53
>
private readonly _mcpService: IMcpService,
54
>
private readonly _logService: ILogService,
55
>
private readonly _startupGracePeriodMs = 5000,
56
>
) {
57
>
super();
58
>
this._logService.debug('[McpGateway][ToolBroker] Initialized');
59
>
60
>
let toolsInitialized = false;
61
>
this._register(autorun(reader => {
62
>
for (const server of this._mcpService.servers.read(reader)) {
63
>
server.tools.read(reader);
64
>
}
65
>
66
>
if (toolsInitialized) {
67
>
this._logService.debug('[McpGateway][ToolBroker] Tools changed, firing onDidChangeTools');
68
>
this._onDidChangeTools.fire();
69
>
} else {
70
>
toolsInitialized = true;
71
>
}
72
>
}));
73
>
74
>
let resourcesInitialized = false;
75
>
this._register(autorun(reader => {
76
>
for (const server of this._mcpService.servers.read(reader)) {
77
>
server.capabilities.read(reader);
78
>
}
79
>
80
>
if (resourcesInitialized) {
81
>
this._logService.debug('[McpGateway][ToolBroker] Resources changed, firing onDidChangeResources');
82
>
this._onDidChangeResources.fire();
83
>
} else {
84
>
resourcesInitialized = true;
85
>
}
86
>
}));
87
>
88
>
let serversInitialized = false;
89
>
this._register(autorun(reader => {
90
>
const servers = this._mcpService.servers.read(reader);
91
>
92
>
if (serversInitialized) {
93
>
this._logService.debug('[McpGateway][ToolBroker] Servers changed, firing onDidChangeServers');
94
>
this._onDidChangeServers.fire(servers.map(s => ({ id: s.definition.id, label: s.definition.label })));
95
>
} else {
96
>
serversInitialized = true;
97
>
}
98
>
}));
99
>
}
100
>
101
>
private _getServerById(serverId: string): IMcpServer | undefined {
102
for (const server of this._mcpService.servers.get()) {
103
if (server.definition.id === serverId) {