Skip to content

Commit 6957717

Browse files
Enable support for Http based MCP resources (#276580)
* Enabling Http enabled MCP resources * Updating with review comments * tidy nits --------- Co-authored-by: Connor Peet <[email protected]>
1 parent bd64c17 commit 6957717

File tree

2 files changed

+42
-4
lines changed

2 files changed

+42
-4
lines changed

src/vs/workbench/contrib/mcp/common/mcpResourceFilesystem.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ import { equalsIgnoreCase } from '../../../../base/common/strings.js';
1717
import { URI } from '../../../../base/common/uri.js';
1818
import { createFileSystemProviderError, FileChangeType, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileChange, IFileDeleteOptions, IFileOverwriteOptions, IFileReadStreamOptions, IFileService, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileWriteOptions, IStat, IWatchOptions } from '../../../../platform/files/common/files.js';
1919
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
20+
import { IWebContentExtractorService } from '../../../../platform/webContentExtractor/common/webContentExtractor.js';
2021
import { IWorkbenchContribution } from '../../../common/contributions.js';
2122
import { McpServer } from './mcpServer.js';
2223
import { McpServerRequestHandler } from './mcpServerRequestHandler.js';
2324
import { IMcpService, McpCapability, McpResourceURI } from './mcpTypes.js';
25+
import { canLoadMcpNetworkResourceDirectly } from './mcpTypesUtils.js';
2426
import { MCP } from './modelContextProtocol.js';
2527

2628
const MOMENTARY_CACHE_DURATION = 3000;
@@ -65,6 +67,7 @@ export class McpResourceFilesystem extends Disposable implements IWorkbenchContr
6567
constructor(
6668
@IInstantiationService private readonly _instantiationService: IInstantiationService,
6769
@IFileService private readonly _fileService: IFileService,
70+
@IWebContentExtractorService private readonly _webContentExtractorService: IWebContentExtractorService,
6871
) {
6972
super();
7073
this._register(this._fileService.registerProvider(McpResourceURI.scheme, this));
@@ -164,7 +167,6 @@ export class McpResourceFilesystem extends Disposable implements IWorkbenchContr
164167
if (forSameURI.length > 0) {
165168
throw createFileSystemProviderError(`File is not a directory`, FileSystemProviderErrorCode.FileNotADirectory);
166169
}
167-
168170
const resourcePathParts = resourceURI.pathname.split('/');
169171

170172
const output = new Map<string, FileType>();
@@ -273,12 +275,26 @@ export class McpResourceFilesystem extends Disposable implements IWorkbenchContr
273275

274276
private async _readURIInner(uri: URI, token?: CancellationToken): Promise<IReadData> {
275277
const { resourceURI, server } = this._decodeURI(uri);
276-
const res = await McpServer.callOn(server, r => r.readResource({ uri: resourceURI.toString() }, token), token);
278+
const matchedServer = this._mcpService.servers.get().find(s => s.definition.id === server.definition.id);
279+
280+
//check for http/https resources and use web content extractor service to fetch the contents.
281+
if (canLoadMcpNetworkResourceDirectly(resourceURI, matchedServer)) {
282+
const extractURI = URI.parse(resourceURI.toString());
283+
const result = (await this._webContentExtractorService.extract([extractURI], { followRedirects: false })).at(0);
284+
if (result?.status === 'ok') {
285+
return {
286+
contents: [{ uri: resourceURI.toString(), text: result.result }],
287+
resourceURI,
288+
forSameURI: [{ uri: resourceURI.toString(), text: result.result }]
289+
};
290+
}
291+
}
277292

293+
const res = await McpServer.callOn(server, r => r.readResource({ uri: resourceURI.toString() }, token), token);
278294
return {
279295
contents: res.contents,
280296
resourceURI,
281-
forSameURI: res.contents.filter(c => equalsUrlPath(c.uri, resourceURI)),
297+
forSameURI: res.contents.filter(c => equalsUrlPath(c.uri, resourceURI))
282298
};
283299
}
284300
}

src/vs/workbench/contrib/mcp/common/mcpTypesUtils.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import { CancellationError } from '../../../../base/common/errors.js';
99
import { DisposableStore } from '../../../../base/common/lifecycle.js';
1010
import { autorun } from '../../../../base/common/observable.js';
1111
import { ToolDataSource } from '../../chat/common/languageModelToolsService.js';
12-
import { IMcpServer, IMcpServerStartOpts, IMcpService, McpConnectionState, McpServerCacheState } from './mcpTypes.js';
12+
import { IMcpServer, IMcpServerStartOpts, IMcpService, McpConnectionState, McpServerCacheState, McpServerTransportType } from './mcpTypes.js';
13+
1314

1415
/**
1516
* Waits up to `timeout` for a server passing the filter to be discovered,
@@ -91,3 +92,24 @@ export function mcpServerToSourceData(server: IMcpServer): ToolDataSource {
9192
definitionId: server.definition.id
9293
};
9394
}
95+
96+
97+
/**
98+
* Validates whether the given HTTP or HTTPS resource is allowed for the specified MCP server.
99+
*
100+
* @param resource The URI of the resource to validate.
101+
* @param server The MCP server instance to validate against, or undefined.
102+
* @returns True if the resource request is valid for the server, false otherwise.
103+
*/
104+
export function canLoadMcpNetworkResourceDirectly(resource: URL, server: IMcpServer | undefined) {
105+
let isResourceRequestValid = false;
106+
if (resource.protocol === 'http:') {
107+
const launch = server?.connection.get()?.launchDefinition;
108+
if (launch && launch.type === McpServerTransportType.HTTP && launch.uri.authority.toLowerCase() === resource.hostname.toLowerCase()) {
109+
isResourceRequestValid = true;
110+
}
111+
} else if (resource.protocol === 'https:') {
112+
isResourceRequestValid = true;
113+
}
114+
return isResourceRequestValid;
115+
}

0 commit comments

Comments
 (0)