Skip to content

Commit 901c932

Browse files
JonasHelmingsdirix
andauthored
Add initial support for MCP (#14598)
* Add initial support for MCP fixed #14523 Signed-off-by: Jonas Helming <[email protected]> Co-authored-by: Stefan Dirix <[email protected]>
1 parent f11c19a commit 901c932

21 files changed

+1012
-9
lines changed

examples/browser/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@theia/ai-history": "1.56.0",
3131
"@theia/ai-huggingface": "1.56.0",
3232
"@theia/ai-llamafile": "1.56.0",
33+
"@theia/ai-mcp": "1.56.0",
3334
"@theia/ai-ollama": "1.56.0",
3435
"@theia/ai-openai": "1.56.0",
3536
"@theia/ai-terminal": "1.56.0",

examples/browser/tsconfig.json

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
{
3333
"path": "../../packages/ai-llamafile"
3434
},
35+
{
36+
"path": "../../packages/ai-mcp"
37+
},
3538
{
3639
"path": "../../packages/ai-ollama"
3740
},

packages/ai-core/src/browser/prompttemplate-contribution.ts

+6-7
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
1515
// *****************************************************************************
1616

17-
import { inject, injectable, named } from '@theia/core/shared/inversify';
17+
import { inject, injectable } from '@theia/core/shared/inversify';
1818
import { GrammarDefinition, GrammarDefinitionProvider, LanguageGrammarDefinitionContribution, TextmateRegistry } from '@theia/monaco/lib/browser/textmate';
1919
import * as monaco from '@theia/monaco-editor-core';
20-
import { Command, CommandContribution, CommandRegistry, ContributionProvider, MessageService } from '@theia/core';
20+
import { Command, CommandContribution, CommandRegistry, MessageService } from '@theia/core';
2121
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
2222

2323
import { codicon, Widget } from '@theia/core/lib/browser';
2424
import { EditorWidget, ReplaceOperation } from '@theia/editor/lib/browser';
25-
import { PromptCustomizationService, PromptService, ToolProvider } from '../common';
25+
import { PromptCustomizationService, PromptService, ToolInvocationRegistry } from '../common';
2626
import { ProviderResult } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
2727

2828
const PROMPT_TEMPLATE_LANGUAGE_ID = 'theia-ai-prompt-template';
@@ -56,9 +56,8 @@ export class PromptTemplateContribution implements LanguageGrammarDefinitionCont
5656
@inject(PromptCustomizationService)
5757
protected readonly customizationService: PromptCustomizationService;
5858

59-
@inject(ContributionProvider)
60-
@named(ToolProvider)
61-
private toolProviders: ContributionProvider<ToolProvider>;
59+
@inject(ToolInvocationRegistry)
60+
protected readonly toolInvocationRegistry: ToolInvocationRegistry;
6261

6362
readonly config: monaco.languages.LanguageConfiguration =
6463
{
@@ -115,7 +114,7 @@ export class PromptTemplateContribution implements LanguageGrammarDefinitionCont
115114
model,
116115
position,
117116
'~{',
118-
this.toolProviders.getContributions().map(provider => provider.getTool()),
117+
this.toolInvocationRegistry.getAllFunctions(),
119118
monaco.languages.CompletionItemKind.Function,
120119
tool => tool.id,
121120
tool => tool.name,

packages/ai-core/src/common/language-model.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,40 @@ export const isLanguageModelRequestMessage = (obj: unknown): obj is LanguageMode
3232
'query' in obj &&
3333
typeof (obj as { query: unknown }).query === 'string'
3434
);
35+
export type ToolRequestParametersProperties = Record<string, { type: string, [key: string]: unknown }>;
36+
export interface ToolRequestParameters {
37+
type?: 'object';
38+
properties: ToolRequestParametersProperties
39+
}
3540
export interface ToolRequest {
3641
id: string;
3742
name: string;
38-
parameters?: { type?: 'object', properties: Record<string, { type: string, [key: string]: unknown }> };
43+
parameters?: ToolRequestParameters
3944
description?: string;
4045
handler: (arg_string: string) => Promise<unknown>;
46+
providerName?: string;
4147
}
48+
49+
export namespace ToolRequest {
50+
export function isToolRequestParametersProperties(obj: unknown): obj is ToolRequestParametersProperties {
51+
if (!obj || typeof obj !== 'object') { return false; };
52+
53+
return Object.entries(obj).every(([key, value]) =>
54+
typeof key === 'string' &&
55+
value &&
56+
typeof value === 'object' &&
57+
'type' in value &&
58+
typeof value.type === 'string' &&
59+
Object.keys(value).every(k => typeof k === 'string')
60+
);
61+
}
62+
export function isToolRequestParameters(obj: unknown): obj is ToolRequestParameters {
63+
return !!obj && typeof obj === 'object' &&
64+
(!('type' in obj) || obj.type === 'object') &&
65+
'properties' in obj && isToolRequestParametersProperties(obj.properties);
66+
}
67+
}
68+
4269
export interface LanguageModelRequest {
4370
messages: LanguageModelRequestMessage[],
4471
tools?: ToolRequest[];

packages/ai-core/src/common/tool-invocation-registry.ts

+46
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,44 @@ export const ToolInvocationRegistry = Symbol('ToolInvocationRegistry');
2424
* Registry for all the function calls available to Agents.
2525
*/
2626
export interface ToolInvocationRegistry {
27+
/**
28+
* Registers a tool into the registry.
29+
*
30+
* @param tool - The `ToolRequest` object representing the tool to be registered.
31+
*/
2732
registerTool(tool: ToolRequest): void;
2833

34+
/**
35+
* Retrieves a specific `ToolRequest` from the registry.
36+
*
37+
* @param toolId - The unique identifier of the tool to retrieve.
38+
* @returns The `ToolRequest` object corresponding to the provided tool ID,
39+
* or `undefined` if the tool is not found in the registry.
40+
*/
2941
getFunction(toolId: string): ToolRequest | undefined;
3042

43+
/**
44+
* Retrieves multiple `ToolRequest`s from the registry.
45+
*
46+
* @param toolIds - A list of tool IDs to retrieve.
47+
* @returns An array of `ToolRequest` objects for the specified tool IDs.
48+
* If a tool ID is not found, it is skipped in the returned array.
49+
*/
3150
getFunctions(...toolIds: string[]): ToolRequest[];
51+
52+
/**
53+
* Retrieves all `ToolRequest`s currently registered in the registry.
54+
*
55+
* @returns An array of all `ToolRequest` objects in the registry.
56+
*/
57+
getAllFunctions(): ToolRequest[];
58+
59+
/**
60+
* Unregisters all tools provided by a specific tool provider.
61+
*
62+
* @param providerName - The name of the tool provider whose tools should be removed (as specificed in the `ToolRequest`).
63+
*/
64+
unregisterAllTools(providerName: string): void;
3265
}
3366

3467
export const ToolProvider = Symbol('ToolProvider');
@@ -52,6 +85,19 @@ export class ToolInvocationRegistryImpl implements ToolInvocationRegistry {
5285
});
5386
}
5487

88+
unregisterAllTools(providerName: string): void {
89+
const toolsToRemove: string[] = [];
90+
for (const [id, tool] of this.tools.entries()) {
91+
if (tool.providerName === providerName) {
92+
toolsToRemove.push(id);
93+
}
94+
}
95+
toolsToRemove.forEach(id => this.tools.delete(id));
96+
}
97+
getAllFunctions(): ToolRequest[] {
98+
return Array.from(this.tools.values());
99+
}
100+
55101
registerTool(tool: ToolRequest): void {
56102
if (this.tools.has(tool.id)) {
57103
console.warn(`Function with id ${tool.id} is already registered.`);

packages/ai-mcp/.eslintrc.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/** @type {import('eslint').Linter.Config} */
2+
module.exports = {
3+
extends: [
4+
'../../configs/build.eslintrc.json'
5+
],
6+
parserOptions: {
7+
tsconfigRootDir: __dirname,
8+
project: 'tsconfig.json'
9+
}
10+
};

packages/ai-mcp/README.md

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Model Context Server (MCP) Integration
2+
3+
The AI MCP package provides an integration that allows users to start and use MCP servers to provide additional tool functions to LLMs, e.g. search or file access (outside of the workspace).
4+
5+
## Features
6+
- Add MCP servers via settings.json
7+
- Start and stop MCP servers.
8+
- Use tool functions provided by MCP servers in prompt templates
9+
10+
## Commands
11+
12+
### Start MCP Server
13+
14+
- **Command ID:** `mcp.startserver`
15+
- **Label:** `MCP: Start MCP Server`
16+
- **Functionality:** Allows you to start a MCP server by selecting from a list of configured servers.
17+
18+
### Stop MCP Server
19+
20+
- **Command ID:** `mcp.stopserver`
21+
- **Label:** `MCP: Stop MCP Server`
22+
- **Functionality:** Allows you to stop a running MCP server by selecting from a list of currently running servers.
23+
24+
## Usage
25+
26+
1. **Starting a MCP Server:**
27+
28+
- Use the command palette to invoke `MCP: Start MCP Server`.
29+
- A quick pick menu will appear with a list of configured MCP servers.
30+
- Select a server to start.
31+
32+
2. **Stopping a MCP Server:**
33+
- Use the command palette to invoke `MCP: Stop MCP Server`.
34+
- A quick pick menu will display a list of currently running MCP servers.
35+
- Select a server to stop.
36+
37+
3. **Using provided tool functions**
38+
- Only functions of started MCP servers can be used
39+
- Open a prompt template and add the added tool functions
40+
- Type '~{' to open the auto completion
41+
42+
## Configuration
43+
44+
Make sure to configure your MCP servers properly within the preference settings.
45+
46+
Example Configuration:
47+
48+
```json
49+
{
50+
"ai-features.mcp.mcpServers": {
51+
"memory": {
52+
"command": "npx",
53+
"args": [
54+
"-y",
55+
"@modelcontextprotocol/server-memory"
56+
]
57+
},
58+
"brave-search": {
59+
"command": "npx",
60+
"args": [
61+
"-y",
62+
"@modelcontextprotocol/server-brave-search"
63+
],
64+
"env": {
65+
"BRAVE_API_KEY": "YOUR_API_KEY"
66+
}
67+
},
68+
"filesystem": {
69+
"command": "npx",
70+
"args": [
71+
"-y",
72+
"@modelcontextprotocol/server-filesystem",
73+
"ABSOLUTE_PATH_TO_ALLOWED_DIRECTORY",
74+
]
75+
},
76+
}
77+
}
78+
```
79+
80+
Example prompt (for search)
81+
```md
82+
~{mcp_brave-search_brave_web_search}
83+
```
84+
85+
Example User query
86+
```md
87+
Search the internet for XYZ
88+
```
89+
90+
## More Information
91+
[List of available MCP servers](https://github.com/modelcontextprotocol/servers)

packages/ai-mcp/package.json

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "@theia/ai-mcp",
3+
"version": "1.56.0",
4+
"description": "Theia - MCP Integration",
5+
"dependencies": {
6+
"@theia/core": "1.56.0",
7+
"@theia/ai-core": "1.56.0",
8+
"@modelcontextprotocol/sdk": "1.0.1"
9+
},
10+
"publishConfig": {
11+
"access": "public"
12+
},
13+
"theiaExtensions": [
14+
{
15+
"frontend": "lib/browser/mcp-frontend-module",
16+
"backend": "lib/node/mcp-backend-module"
17+
}
18+
],
19+
"keywords": [
20+
"theia-extension"
21+
],
22+
"license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
23+
"repository": {
24+
"type": "git",
25+
"url": "https://github.com/eclipse-theia/theia.git"
26+
},
27+
"bugs": {
28+
"url": "https://github.com/eclipse-theia/theia/issues"
29+
},
30+
"homepage": "https://github.com/eclipse-theia/theia",
31+
"files": [
32+
"lib",
33+
"src"
34+
],
35+
"scripts": {
36+
"build": "theiaext build",
37+
"clean": "theiaext clean",
38+
"compile": "theiaext compile",
39+
"lint": "theiaext lint",
40+
"test": "theiaext test",
41+
"watch": "theiaext watch"
42+
},
43+
"devDependencies": {
44+
"@theia/ext-scripts": "1.56.0"
45+
},
46+
"nyc": {
47+
"extends": "../../configs/nyc.json"
48+
}
49+
}

0 commit comments

Comments
 (0)