mcpServer.ts ×23

Frontier kind: Code frontier

unlabeled · c_6c524c9d39c8

9 tests · 41193 LOC · 190 files · introduces 0 tests · 552 LOC · 9 files

Introduces — evidence that enters the hierarchy at this concept

Code
61 ranges552 lines · 9 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
3863 ranges41193 lines · 190 files · Browse complete extent
All tests (intent)
9 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

9 files ranked by introduced lines: 552 introduced LOC across 61 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/contrib/mcp/common/mcpServer.ts 225 introduced LOC · 23 ranges

Open complete file

153
154 constructor(
155 > scope: StorageScope, mcpServer.ts
156 > @IStorageService storageService: IStorageService,
157 > ) {
158 > super();
159 >
160 > type StoredType = {
161 > extensionServers: [string, IServerCacheEntry][];
162 > serverTools: [string, IToolCacheEntry][];
163 > };
164 >
165 > const storageKey = 'mcpToolCache';
166 > this._register(storageService.onWillSaveState(() => {
167 if (this.didChange) {
168 storageService.store(storageKey, {
172 this.didChange = false;
173 }
174 > })); mcpServer.ts
175 >
176 > try {
177 > const cached: StoredType | undefined = storageService.getObject(storageKey, scope);
178 > this.extensionServers = new Map(cached?.extensionServers ?? []);
179 > cached?.serverTools?.forEach(([k, v]) => this.cache.set(k, v));
180 > } catch {
181 // ignored
182 }
183 > } mcpServer.ts
184
185 /** Resets the cache for primitives and extension servers */
192 /** Gets cached primitives for a server (used before a server is running) */
193 get(definitionId: string) {
194 > return this.cache.get(definitionId); mcpServer.ts
195 > }
196
197 /** Sets cached primitives for a server */
312 */
313 constructor(
314 > private readonly _definitionId: string, mcpServer.ts
315 > private readonly _cache: McpServerMetadataCache,
316 > private readonly _fromStaticDefinition: IObservable<C | undefined> | undefined,
317 > private readonly _fromCache: (entry: IToolCacheEntry) => C,
318 > private readonly _toT: (values: C, reader: IDerivedReader<void>) => T,
319 > private readonly defaultValue: C,
320 > ) { }
321 >
322 > public get fromCache(): { nonce: string | undefined; data: C } | undefined {
323 > const c = this._cache.get(this._definitionId);
324 > return c ? { data: this._fromCache(c), nonce: c.nonce } : undefined;
325 > }
326 >
327 > public hasStaticDefinition(reader: IReader | undefined) {
328 return !!this._fromStaticDefinition?.read(reader);
329 }
330 > mcpServer.ts
331 > public readonly fromServerPromise = observableValue<ObservablePromise<{
332 > readonly data: C;
333 > readonly nonce: string | undefined;
334 > }> | undefined>(this, undefined);
335 >
336 > private readonly fromServer = derived(reader => this.fromServerPromise.read(reader)?.promiseResult.read(reader)?.data);
337 >
338 > public readonly value: IObservable<T> = derived(reader => {
339 > const serverTools = this.fromServer.read(reader);
340 > const definitions = serverTools?.data ?? this._fromStaticDefinition?.read(reader) ?? this.fromCache?.data ?? this.defaultValue;
341 > return this._toT(definitions, reader);
342 > });
343 }
344
510
511 constructor(
512 > initialCollection: McpCollectionDefinition, mcpServer.ts
513 > public readonly definition: McpDefinitionReference,
514 > explicitRoots: URI[] | undefined,
515 > private readonly _requiresExtensionActivation: boolean | undefined,
516 > private readonly _primitiveCache: McpServerMetadataCache,
517 > prefixGenerator: McpPrefixGenerator,
518 > enablementModel: IEnablementModel,
519 > @IMcpRegistry private readonly _mcpRegistry: IMcpRegistry,
520 > @IAllowedMcpServersService private readonly _allowedMcpServersService: IAllowedMcpServersService,
521 > @IWorkspaceContextService workspacesService: IWorkspaceContextService,
522 > @IExtensionService private readonly _extensionService: IExtensionService,
523 > @ILoggerService private readonly _loggerService: ILoggerService,
524 > @IOutputService private readonly _outputService: IOutputService,
525 > @ITelemetryService private readonly _telemetryService: ITelemetryService,
526 > @ICommandService private readonly _commandService: ICommandService,
527 > @IInstantiationService private readonly _instantiationService: IInstantiationService,
528 > @IDialogService private readonly _dialogService: IDialogService,
529 > @INotificationService private readonly _notificationService: INotificationService,
530 > @IOpenerService private readonly _openerService: IOpenerService,
531 > @IMcpSamplingService private readonly _samplingService: IMcpSamplingService,
532 > @IMcpElicitationService private readonly _elicitationService: IMcpElicitationService,
533 > @IMcpSandboxService private readonly _mcpSandboxService: IMcpSandboxService,
534 > @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
535 > ) {
536 > super();
537 >
538 > this.collection = initialCollection;
539 > this._fullDefinitions = this._mcpRegistry.getServerDefinition(this.collection, this.definition);
540 > this.enablement = derived(r => enablementModel.readEnabled(definition.id, r));
541 >
542 > this._policyEpoch = observableFromEvent(this, this._allowedMcpServersService.onDidChangeAllowedMcpServers, () => undefined);
543 > this._policyBlock = derived<McpConnectionState.Error | undefined>(this, reader => {
544 > this._policyEpoch.read(reader);
545 > const connection = this._connection.read(reader);
546 > if (connection) {
547 // Authoritative: the connection carries the fully resolved launch.
548 return this._evaluatePolicy(this._identityFromLaunch(connection.launchDefinition));
549 }
550 > // At rest, only decide when we have a concrete, fully-resolved launch. If the definition mcpServer.ts
551 > // has not been provided yet (e.g. a lazy/extension server before activation) or the launch
552 > // still contains unresolved `${...}` variables (inputs, workspace or env vars), a
553 > // URL/command allow/deny rule cannot be matched reliably, so defer the decision to start()
554 > // — which re-checks the fully resolved launch — to avoid over-eagerly blocking (and hiding
555 > // the cached tools of) a server that will actually be allowed once resolved. `chat.mcp.access`
556 > // and deny-by-name are still enforced at start(), and access also by the enablement layer.
557 > const launch = this._fullDefinitions.read(reader).server?.launch;
558 > if (!launch) {
559 return undefined;
560 }
561 > const identity = this._identityFromLaunch(launch); mcpServer.ts
562 > if (McpServer._hasUnresolvedVariables(identity)) {
563 return undefined;
564 }
565 > return this._evaluatePolicy(identity); mcpServer.ts
566 > });
567 >
568 > // Stop a live connection when the policy blocks it (e.g. the policy was tightened while the
569 > // server was running). The block itself is evaluated reactively by `_policyBlock`, which also
570 > // hides cached tools/prompts and surfaces the reason in the UI.
571 > this._register(autorun(reader => {
572 > if (this._policyBlock.read(reader) && this._connection.read(undefined)) {
573 this._connection.set(undefined, undefined); // disposes and stops the connection
574 }
575 > })); mcpServer.ts
576 >
577 > this._loggerId = `mcpServer.${definition.id}`;
578 > this._logger = this._register(_loggerService.createLogger(this._loggerId, { hidden: true, name: `MCP: ${definition.label}` }));
579 >
580 > const that = this;
581 > this._register(this._instantiationService.createInstance(McpDevModeServerAttache, this, { get lastModeDebugged() { return that._lastModeDebugged; } }));
582 >
583 > // If the logger is disposed but not deregistered, then the disposed instance
584 > // is reused and no-ops. todo@sandy081 this seems like a bug.
585 > this._register(toDisposable(() => _loggerService.deregisterLogger(this._loggerId)));
586 >
587 > // 1. Reflect workspaces into the MCP roots
588 > const workspaces = explicitRoots
589 ? observableValue(this, explicitRoots.map(uri => ({ uri, name: basename(uri) })))
590 > : observableFromEvent( mcpServer.ts
591 > this,
592 > workspacesService.onDidChangeWorkspaceFolders,
593 > () => workspacesService.getWorkspace().folders,
594 > );
595 >
596 > const uriTransformer = environmentService.remoteAuthority ? createURITransformer(environmentService.remoteAuthority) : undefined;
597 >
598 > this._register(autorun(reader => {
599 > const cnx = this._connection.read(reader)?.handler.read(reader);
600 > if (!cnx) {
601 > return;
602 > }
603
604 cnx.roots = workspaces.read(reader)
612 return { name: w.name, uri: uri.toString() };
613 });
614 > })); mcpServer.ts
615 >
616 > // 2. Populate this.tools when we connect to a server.
617 > this._register(autorun(reader => {
618 > const cnx = this._connection.read(reader);
619 > const handler = cnx?.handler.read(reader);
620 > if (handler) {
621 this._populateLiveData(handler, cnx?.definition.cacheNonce, reader.store);
622 > } else if (this._tools) { mcpServer.ts
623 this.resetLiveData();
624 }
625 > })); mcpServer.ts
626 >
627 > this._register(autorun(reader => {
628 > const cnx = this._connection.read(reader);
629 > this._potentialSandboxBlockListener.value = cnx?.onPotentialSandboxBlock(block => this.recordPotentialSandboxBlock(block));
630 > }));
631 >
632 > const staticMetadata = derived(reader => {
633 > const def = this._fullDefinitions.read(reader).server;
634 > return def && def.cacheNonce !== this._tools.fromCache?.nonce ? def.staticMetadata : undefined;
635 > });
636 >
637 > this._serverMetadata = new CachedPrimitive<ServerMetadata, StoredServerMetadata | undefined>(
638 > this.definition.id,
639 > this._primitiveCache,
640 > staticMetadata.map(m => m ? this._toStoredMetadata(m?.serverInfo, m?.instructions) : undefined),
641 > (entry) => ({ serverName: entry.serverName, serverInstructions: entry.serverInstructions, serverIcons: entry.serverIcons }),
642 > (entry) => ({ serverName: entry?.serverName, serverInstructions: entry?.serverInstructions, icons: McpIcons.fromStored(entry?.serverIcons) }),
643 > undefined,
644 > );
645 >
646 > // Form the tool prefix from the server-announced name when known so that
647 > // registry-style mcp.json keys like `io.github.upstash/context7` don't end
648 > // up in `mcp_io_github_ups_*` truncated names. See #299749.
649 > const preferredName = derived(reader => this._serverMetadata.value.read(reader)?.serverName || this.definition.label);
650 > const prefixRef = derivedDisposable(reader => prefixGenerator.take(preferredName.read(reader)));
651 > const toolPrefix = prefixRef.map(ref => ref.object);
652 >
653 > // 3. Publish tools
654 > this._tools = new CachedPrimitive<readonly IMcpTool[], readonly ValidatedMcpTool[]>(
655 > this.definition.id,
656 > this._primitiveCache,
657 > staticMetadata
658 > .map(m => {
659 const tools = m?.tools?.filter(t => t.availability === McpServerStaticToolAvailability.Initial).map(t => t.definition);
660 return tools?.length ? new ObservablePromise(this._getValidatedTools(tools)) : undefined;
661 > }) mcpServer.ts
662 > .map((o, reader) => o?.promiseResult.read(reader)?.data),
663 > (entry) => entry.tools,
664 > (entry, reader) => entry.map(def => this._instantiationService.createInstance(McpTool, this, toolPrefix.read(reader), def)).sort((a, b) => a.compare(b)),
665 > [],
666 > );
667 >
668 > // 4. Publish prompts
669 > this._prompts = new CachedPrimitive<readonly IMcpPrompt[], readonly StoredMcpPrompt[]>(
670 > this.definition.id,
671 > this._primitiveCache,
672 > undefined,
673 > (entry) => entry.prompts || [],
674 > (entry) => entry.map(e => new McpPrompt(this, e)),
675 > [],
676 > );
677 >
678 > this._capabilities = new CachedPrimitive<number | undefined, number | undefined>(
679 > this.definition.id,
680 > this._primitiveCache,
681 > staticMetadata.map(m => m?.capabilities !== undefined ? encodeCapabilities(m.capabilities) : undefined),
682 > (entry) => entry.capabilities,
683 > (entry) => entry,
684 > undefined,
685 > );
686 >
687 > // Hold the prefix for the lifetime of the server so its tool name stays
688 > // stable even when no one is currently observing the tools list.
689 > prefixRef.recomputeInitiallyAndOnChange(this._store);
690 > }
691
692 public readDefinitions(): IObservable<{ server: McpServerDefinition | undefined; collection: McpCollectionDefinition | undefined }> {
693 > return this._fullDefinitions; mcpServer.ts
694 > }
695
696 public showOutput(preserveFocus?: boolean) {
721
722 private _identityFromLaunch(launch: McpServerLaunch | undefined): IMcpServerIdentity {
723 > if (launch?.type === McpServerTransportType.HTTP) { mcpServer.ts
724 return { name: this.definition.label, url: launch.uri.toString(true) };
725 }
726 > if (launch?.type === McpServerTransportType.Stdio) { mcpServer.ts
727 > // `launch.command`/`launch.args` are typed as non-nullable but can be `undefined` at
728 > // runtime when they originate from user/discovery configuration that omitted the field.
729 > // When `command` is present, build the full command line (defaulting `args` to an empty
730 > // array and dropping any non-string entries); the produced `IMcpServerIdentity.command`
731 > // then never contains a non-string entry, which would otherwise break policy matching and
732 > // the unresolved-variable check. Use a string check so a valid-but-empty command string is
733 > // preserved while malformed non-string command values are dropped. When `command` is absent
734 > // the full command line is unknown, so omit the field entirely rather than matching on args
735 > // alone (which could collide with unrelated servers).
736 > return typeof launch.command === 'string'
737 > ? { name: this.definition.label, command: [launch.command, ...(launch.args ?? []).filter(arg => typeof arg === 'string')] }
738 : { name: this.definition.label };
739 > } mcpServer.ts
740 return { name: this.definition.label };
741 > } mcpServer.ts
742
743 private _evaluatePolicy(identity: IMcpServerIdentity): McpConnectionState.Error | undefined {
744 > const allowed = this._allowedMcpServersService.isServerAllowed(identity); mcpServer.ts
745 > return allowed === true ? undefined : { state: McpConnectionState.Kind.Error, message: allowed.value };
746 > }
747
748 /**
753 */
754 private static _hasUnresolvedVariables(identity: IMcpServerIdentity): boolean {
755 > const variableMarker = ConfigurationResolverExpression.VARIABLE_LHS; mcpServer.ts
756 > return !!identity.url?.includes(variableMarker) || !!identity.command?.some(arg => arg.includes(variableMarker));
757 > }
758
759 public start({ interaction, autoTrustChanges, promptType, debug, errorOnUserInteraction }: IMcpServerStartOpts = {}): Promise<McpConnectionState> {
src/vs/workbench/contrib/mcp/common/mcpResourceFilesystem.ts 104 introduced LOC · 15 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- mcpResourceFilesystem.ts
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 { sumBy } from '../../../../base/common/arrays.js';
7 > import { disposableTimeout } from '../../../../base/common/async.js';
8 > import { decodeBase64, VSBuffer } from '../../../../base/common/buffer.js';
9 > import { CancellationToken, CancellationTokenPool, CancellationTokenSource } from '../../../../base/common/cancellation.js';
10 > import { Emitter, Event } from '../../../../base/common/event.js';
11 > import { Lazy } from '../../../../base/common/lazy.js';
12 > import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
13 > import { ResourceMap } from '../../../../base/common/map.js';
14 > import { autorun } from '../../../../base/common/observable.js';
15 > import { newWriteableStream, ReadableStreamEvents } from '../../../../base/common/stream.js';
16 > import { equalsIgnoreCase } from '../../../../base/common/strings.js';
17 > import { URI } from '../../../../base/common/uri.js';
18 > import { createFileSystemProviderError, FileChangeType, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileChange, IFileDeleteOptions, IFileOverwriteOptions, IFileReadStreamOptions, IFileService, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileWriteOptions, IStat, IWatchOptions } from '../../../../platform/files/common/files.js';
19 > import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
20 > import { IWebContentExtractorService } from '../../../../platform/webContentExtractor/common/webContentExtractor.js';
21 > import { IWorkbenchContribution } from '../../../common/contributions.js';
22 > import { McpServer } from './mcpServer.js';
23 > import { McpServerRequestHandler } from './mcpServerRequestHandler.js';
24 > import { IMcpService, McpCapability, McpResourceURI } from './mcpTypes.js';
25 > import { canLoadMcpNetworkResourceDirectly } from './mcpTypesUtils.js';
26 > import { MCP } from './modelContextProtocol.js';
27 >
28 > const MOMENTARY_CACHE_DURATION = 3000;
29 >
30 > interface IReadData {
31 > contents: (MCP.TextResourceContents | MCP.BlobResourceContents)[];
32 > resourceURI: URL;
33 > forSameURI: (MCP.TextResourceContents | MCP.BlobResourceContents)[];
34 > }
35 >
36 > export class McpResourceFilesystem extends Disposable implements IWorkbenchContribution,
37 > IFileSystemProviderWithFileReadWriteCapability,
38 > IFileSystemProviderWithFileAtomicReadCapability,
39 > IFileSystemProviderWithFileReadStreamCapability {
40 > /** Defer getting the MCP service since this is a BlockRestore and no need to make it unnecessarily. */
41 > private readonly _mcpServiceLazy = new Lazy(() => this._instantiationService.invokeFunction(a => a.get(IMcpService)));
42 >
43 > /**
44 > * For many file operations we re-read the resources quickly (e.g. stat
45 > * before reading the file) and would prefer to avoid spamming the MCP
46 > * with multiple reads. This is a very short-duration cache
47 > * to solve that.
48 > */
49 > private readonly _momentaryCache = new ResourceMap<{ pool: CancellationTokenPool; promise: Promise<IReadData> }>();
50 >
51 > private get _mcpService() {
52 > return this._mcpServiceLazy.value;
53 > }
54 >
55 > public readonly onDidChangeCapabilities = Event.None;
56 >
57 > private readonly _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
58 > public readonly onDidChangeFile = this._onDidChangeFile.event;
59 >
60 > public readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.None
61 > | FileSystemProviderCapabilities.Readonly
62 > | FileSystemProviderCapabilities.PathCaseSensitive
63 > | FileSystemProviderCapabilities.FileReadStream
64 > | FileSystemProviderCapabilities.FileAtomicRead
65 > | FileSystemProviderCapabilities.FileReadWrite;
66 >
67 > constructor(
68 > @IInstantiationService private readonly _instantiationService: IInstantiationService,
69 > @IFileService private readonly _fileService: IFileService,
70 > @IWebContentExtractorService private readonly _webContentExtractorService: IWebContentExtractorService,
71 > ) {
72 > super();
73 > this._register(this._fileService.registerProvider(McpResourceURI.scheme, this));
74 > }
75 >
76 > //#region Filesystem API
77 >
78 > public async readFile(resource: URI): Promise<Uint8Array> {
79 return this._readFile(resource);
80 }
82 > public readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents<Uint8Array> {
83 const stream = newWriteableStream<Uint8Array>(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer);
84
100 return stream;
101 }
103 > public watch(uri: URI, _opts: IWatchOptions): IDisposable {
104 const { resourceURI, server } = this._decodeURI(uri);
105 const cap = server.capabilities.get();
148 return store;
149 }
151 > public async stat(resource: URI): Promise<IStat> {
152 const { forSameURI, contents } = await this._readURI(resource);
153 if (!contents.length) {
162 };
163 }
165 > public async readdir(resource: URI): Promise<[string, FileType][]> {
166 const { forSameURI, contents, resourceURI } = await this._readURI(resource);
167 if (forSameURI.length > 0) {
194 return [...output];
195 }
197 > public mkdir(resource: URI): Promise<void> {
198 throw createFileSystemProviderError('write is not supported', FileSystemProviderErrorCode.NoPermissions);
199 }
200 > public writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> { mcpResourceFilesystem.ts
201 throw createFileSystemProviderError('write is not supported', FileSystemProviderErrorCode.NoPermissions);
202 }
203 > public delete(resource: URI, opts: IFileDeleteOptions): Promise<void> { mcpResourceFilesystem.ts
204 throw createFileSystemProviderError('delete is not supported', FileSystemProviderErrorCode.NoPermissions);
205 }
206 > public rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise<void> { mcpResourceFilesystem.ts
207 throw createFileSystemProviderError('rename is not supported', FileSystemProviderErrorCode.NoPermissions);
208 }
210 > //#endregion
211 >
212 > private async _readFile(resource: URI, token?: CancellationToken): Promise<Uint8Array> {
213 const { forSameURI, contents } = await this._readURI(resource);
214
225 return contentToBuffer(forSameURI[0]);
226 }
228 > private _decodeURI(uri: URI) {
229 let definitionId: string;
230 let resourceURL: URL;
251 return { definitionId, resourceURI: resourceURL, server };
252 }
254 > private async _readURI(uri: URI, token?: CancellationToken) {
255 const cached = this._momentaryCache.get(uri);
256 if (cached) {
273 return promise;
274 }
276 > private async _readURIInner(uri: URI, token?: CancellationToken): Promise<IReadData> {
277 const { resourceURI, server } = this._decodeURI(uri);
278 const matchedServer = this._mcpService.servers.get().find(s => s.definition.id === server.definition.id);
298 };
299 }
301 >
302 function equalsUrlPath(a: string, b: URL): boolean {
303 // MCP doesn't specify either way, but underlying systems may can be case-sensitive.
305 return equalsIgnoreCase(new URL(a).pathname, b.pathname);
306 }
308 function contentToBuffer(content: MCP.TextResourceContents | MCP.BlobResourceContents): Uint8Array {
309 if ('text' in content) {
src/vs/workbench/contrib/mcp/common/mcpService.ts 63 introduced LOC · 5 ranges

Open complete file

41
42 constructor(
43 > @IInstantiationService private readonly _instantiationService: IInstantiationService, mcpService.ts
44 > @IMcpRegistry private readonly _mcpRegistry: IMcpRegistry,
45 > @ILogService private readonly _logService: ILogService,
46 > @IConfigurationService private readonly configurationService: IConfigurationService,
47 > @IStorageService storageService: IStorageService,
48 > ) {
49 > super();
50 >
51 > const baseEnablement = this._register(new EnablementModel('mcp.enablement', storageService));
52 > const collisionBehavior = observableConfigValue(mcpServerCollisionBehaviorSection, McpCollisionBehavior.Disable, configurationService);
53 > this.enablementModel = new McpCollisionEnablementModel(baseEnablement, this._mcpRegistry, collisionBehavior);
54 >
55 > this.userCache = this._register(_instantiationService.createInstance(McpServerMetadataCache, StorageScope.PROFILE));
56 > this.workspaceCache = this._register(_instantiationService.createInstance(McpServerMetadataCache, StorageScope.WORKSPACE));
57 >
58 > const updateThrottle = this._store.add(new RunOnceScheduler(() => this.updateCollectedServers(), 500));
59 >
60 > // Throttle changes so that if a collection is changed, or a server is
61 > // unregistered/registered, we don't stop servers unnecessarily.
62 > this._register(autorun(reader => {
63 > for (const collection of this._mcpRegistry.collections.read(reader)) {
64 > collection.serverDefinitions.read(reader);
65 > }
66 > updateThrottle.schedule(500);
67 > }));
68 > }
69
70 public cancelAutostart(): void {
177
178 public updateCollectedServers() {
179 > const definitions = this._mcpRegistry.collections.get().flatMap(collectionDefinition => mcpService.ts
180 > collectionDefinition.serverDefinitions.get().map(serverDefinition => {
181 > return { serverDefinition, collectionDefinition };
182 > })
183 > );
184 >
185 > const nextDefinitions = new Set(definitions);
186 > const currentServers = this._servers.get();
187 > const nextServers: IMcpServerRec[] = [];
188 > const pushMatch = (match: (typeof definitions)[0], rec: IMcpServerRec) => {
189 nextDefinitions.delete(match);
190 nextServers.push(rec);
196 }
197 };
199 > // Transfer over any servers that are still valid.
200 > for (const server of currentServers) {
201 const match = definitions.find(d => defsEqual(server.object, d));
202 if (match) {
206 }
207 }
209 > // Create any new servers that are needed.
210 > for (const def of nextDefinitions) {
211 > const object = this._instantiationService.createInstance(
212 > McpServer,
213 > def.collectionDefinition,
214 > def.serverDefinition,
215 > def.serverDefinition.roots,
216 > !!def.collectionDefinition.lazy,
217 > def.collectionDefinition.scope === StorageScope.WORKSPACE ? this.workspaceCache : this.userCache,
218 > this._prefixGenerator,
219 > this.enablementModel,
220 > );
221 >
222 > nextServers.push({ object });
223 > }
224 >
225 > transaction(tx => {
226 > this._servers.set(nextServers, tx);
227 > });
228 > }
229
230 public override dispose(): void {
231 > this._servers.get().forEach(s => s.object.dispose()); mcpService.ts
232 > super.dispose();
233 > }
234 }
235
src/vs/platform/webContentExtractor/common/webContentExtractor.ts 59 introduced LOC · 3 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- webContentExtractor.ts
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 { VSBuffer } from '../../../base/common/buffer.js';
7 > import { CancellationToken } from '../../../base/common/cancellation.js';
8 > import { URI } from '../../../base/common/uri.js';
9 > import { createDecorator } from '../../instantiation/common/instantiation.js';
10 >
11 > export const IWebContentExtractorService = createDecorator<IWebContentExtractorService>('IWebContentExtractorService');
12 > export const ISharedWebContentExtractorService = createDecorator<ISharedWebContentExtractorService>('ISharedWebContentExtractorService');
13 >
14 > export interface IWebContentExtractorOptions {
15 > /**
16 > * Whether to allow cross-authority redirects on the web content.
17 > * 'false' by default.
18 > */
19 > followRedirects?: boolean;
20 >
21 > /**
22 > * List of trusted domain patterns for redirect validation.
23 > */
24 > trustedDomains?: string[];
25 > }
26 >
27 > export type WebContentExtractResult =
28 > | { status: 'ok'; result: string; title?: string }
29 > | { status: 'error'; error: string; statusCode?: number; result?: string; title?: string }
30 > | { status: 'redirect'; toURI: URI };
31 >
32 > export interface IWebContentExtractorService {
33 > _serviceBrand: undefined;
34 > extract(uri: URI[], options?: IWebContentExtractorOptions): Promise<WebContentExtractResult[]>;
35 > }
36 >
37 > /*
38 > * A service that extracts image content from a given arbitrary URI. This is done in the shared process to avoid running non trusted application code in the main process.
39 > */
40 > export interface ISharedWebContentExtractorService {
41 > _serviceBrand: undefined;
42 > readImage(uri: URI, token: CancellationToken): Promise<VSBuffer | undefined>;
43 > }
44 >
45 > /**
46 > * A service that extracts web content from a given URI.
47 > * This is a placeholder implementation that does not perform any actual extraction.
48 > * It's intended to be used on platforms where web content extraction is not supported such as in the browser.
49 > */
50 > export class NullWebContentExtractorService implements IWebContentExtractorService {
51 > _serviceBrand: undefined;
52 >
53 > extract(_uri: URI[]): Promise<WebContentExtractResult[]> {
54 throw new Error('Not implemented');
55 }
57 >
58 > export class NullSharedWebContentExtractorService implements ISharedWebContentExtractorService {
59 > _serviceBrand: undefined;
60 > readImage(_uri: URI, _token: CancellationToken): Promise<VSBuffer | undefined> {
61 throw new Error('Not implemented');
62 }
src/vs/workbench/contrib/mcp/common/mcpDevMode.ts 45 introduced LOC · 5 ranges

Open complete file

22 export class McpDevModeServerAttache extends Disposable {
23 constructor(
24 > server: IMcpServer, mcpDevMode.ts
25 > fwdRef: { lastModeDebugged: boolean },
26 > @IMcpRegistry registry: IMcpRegistry,
27 > @IFileService fileService: IFileService,
28 > @IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
29 > ) {
30 > super();
31 >
32 > const workspaceFolder = server.readDefinitions().map(({ collection }) => collection?.presentation?.origin &&
33 > workspaceContextService.getWorkspaceFolder(collection.presentation?.origin)?.uri);
34 >
35 > const restart = async () => {
36 const lastDebugged = fwdRef.lastModeDebugged;
37 await server.stop();
38 await server.start({ debug: lastDebugged });
39 };
41 > // 1. Auto-start the server, restart if entering debug mode
42 > let didAutoStart = false;
43 > this._register(autorun(reader => {
44 > const defs = server.readDefinitions().read(reader);
45 > if (!defs.collection || !defs.server || !defs.server.devMode) {
46 > didAutoStart = false;
47 > return;
48 > }
49
50 // don't keep trying to start the server unless it's a new server or devmode is newly turned on
60 server.start();
61 didAutoStart = true;
62 > })); mcpDevMode.ts
63 >
64 > const debugMode = server.readDefinitions().map(d => !!d.server?.devMode?.debug);
65 > this._register(autorunDelta(debugMode, ({ lastValue, newValue }) => {
66 > if (!!newValue && !objectsEqual(lastValue, newValue)) {
67 restart();
68 }
69 > })); mcpDevMode.ts
70 >
71 > // 2. Watch for file changes
72 > const watchObs = derivedOpts<string[] | undefined>({ equalsFn: arraysEqual }, reader => {
73 > const def = server.readDefinitions().read(reader);
74 > const watch = def.server?.devMode?.watch;
75 > return typeof watch === 'string' ? [watch] : watch;
76 > });
77 >
78 > const restartScheduler = this._register(new Throttler());
79 >
80 > this._register(autorun(reader => {
81 > const pattern = watchObs.read(reader);
82 > const wf = workspaceFolder.read(reader);
83 > if (!pattern || !wf) {
84 > return;
85 > }
86
87 const includes = pattern.filter(p => !p.startsWith('!'));
src/vs/workbench/contrib/mcp/test/common/mcpRegistryTypes.ts 32 introduced LOC · 4 ranges

Open complete file

161
162 constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { }
164 > _serviceBrand: undefined;
165 > onDidChangeInputs = Event.None;
166 > collections = observableValue<readonly McpCollectionDefinition[]>(this, [{
167 > id: 'test-collection',
168 > remoteAuthority: null,
169 > label: 'Test Collection',
170 > configTarget: ConfigurationTarget.USER,
171 > order: 0,
172 > serverDefinitions: observableValue(this, [{
173 > id: 'test-server',
174 > label: 'Test Server',
175 > launch: { type: McpServerTransportType.Stdio, command: 'echo', args: ['Hello MCP'], env: {}, envFile: undefined, cwd: undefined, sandbox: undefined },
176 > cacheNonce: 'a',
177 > } satisfies McpServerDefinition]),
178 > trustBehavior: McpServerTrust.Kind.Trusted,
179 > scope: StorageScope.APPLICATION,
180 > }]);
181 > delegates = observableValue<readonly IMcpHostDelegate[]>(this, [{
182 > priority: 0,
183 > canStart: () => true,
184 > substituteVariables(serverDefinition, launch) {
185 return Promise.resolve(launch);
186 },
187 > start: () => { mcpRegistryTypes.ts
188 const t = this.makeTestTransport();
189 setTimeout(() => t.setConnectionState({ state: McpConnectionState.Kind.Running }));
190 return t;
191 },
192 > waitForInitialProviderPromises: () => Promise.resolve(), mcpRegistryTypes.ts
193 > }]);
194 > lazyCollectionState = observableValue(this, { state: LazyCollectionState.AllKnown, collections: [] });
195 collectionToolPrefix(collection: McpCollectionReference): IObservable<string> {
196 return observableValue<string>(this, `mcp-${collection.id}-`);
197 }
198 getServerDefinition(collectionRef: McpDefinitionReference, definitionRef: McpDefinitionReference): IObservable<{ server: McpServerDefinition | undefined; collection: McpCollectionDefinition | undefined }> {
199 > const collectionObs = this.collections.map(cols => cols.find(c => c.id === collectionRef.id)); mcpRegistryTypes.ts
200 > return collectionObs.map((collection, reader) => {
201 > const server = collection?.serverDefinitions.read(reader).find(s => s.id === definitionRef.id);
202 > return { collection, server };
203 > });
204 > }
205 discoverCollections(): Promise<McpCollectionDefinition[]> {
206 throw new Error('Method not implemented.');
src/vs/base/common/observableInternal/reactions/autorun.ts 11 introduced LOC · 1 range

Open complete file

118
119 export function autorunDelta<T>(
120 > observable: IObservable<T>, autorun.ts
121 > handler: (args: { lastValue: T | undefined; newValue: T }) => void
122 > ): IDisposable {
123 > let _lastValue: T | undefined;
124 > return autorunOpts({ debugReferenceFn: handler }, (reader) => {
125 > const newValue = observable.read(reader);
126 > const lastValue = _lastValue;
127 > _lastValue = newValue;
128 > handler({ lastValue, newValue });
129 > });
130 > }
131
132 export function autorunIterableDelta<T>(
src/vs/base/common/observableInternal/observables/observableValue.ts 9 introduced LOC · 3 ranges

Open complete file

104
105 export function disposableObservableValue<T extends IDisposable | undefined, TChange = void>(nameOrOwner: string | object, initialValue: T, debugLocation = DebugLocation.ofCaller()): ISettableObservable<T, TChange> & IDisposable {
106 > let debugNameData: DebugNameData; observableValue.ts
107 > if (typeof nameOrOwner === 'string') {
108 debugNameData = new DebugNameData(undefined, nameOrOwner, undefined);
109 > } else { observableValue.ts
110 > debugNameData = new DebugNameData(nameOrOwner, undefined, undefined);
111 > }
112 > return new DisposableObservableValue(debugNameData, initialValue, strictEquals, debugLocation);
113 > }
114
115 export class DisposableObservableValue<T extends IDisposable | undefined, TChange = void> extends ObservableValue<T, TChange> implements IDisposable {
125
126 public dispose(): void {
127 > this._value?.dispose(); observableValue.ts
128 > }
129 }
src/vs/platform/log/common/log.ts 4 introduced LOC · 2 ranges

Open complete file

811
812 deregisterLogger(idOrResource: URI | string): void {
813 > const resource = this.toResource(idOrResource); log.ts
814 > const existing = this._loggers.get(resource);
815 > if (existing) {
816 if (existing.logger) {
817 existing.logger.dispose();
820 this._onDidChangeLoggers.fire({ added: [], removed: [existing.info] });
821 }
822 > } log.ts
823
824 *getRegisteredLoggers(): Iterable<ILoggerResource> {