-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d5a4ab6
commit 504b76c
Showing
6 changed files
with
311 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
index.test.ts | ||
package-lock.json | ||
dist/ | ||
vitest.config.ts | ||
vitest.config.t |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# MCP proxy for OpenCtx | ||
|
||
This is a context provider for [OpenCtx](https://openctx.org) that fetches contents from a [MCP](https://modelcontextprotocol.io) provider for use as context. | ||
|
||
Currently, only MCP over stdio is supported (HTTP is not yet supported). | ||
|
||
## Development | ||
|
||
1. Clone the [modelcontextprotocol/servers](https://github.com/modelcontextprotocol/servers) repository. Follow the instructions there to build the example providers. This should generate output files of the form `build/${example_name}/index.js`. | ||
1. Run `pnpm watch` in this directory. | ||
1. Add the following to your VS Code settings: | ||
```json | ||
"openctx.providers": { | ||
// ...other providers... | ||
"https://openctx.org/npm/@openctx/provider-modelcontextprotocol": { | ||
"nodeCommand": "node", | ||
"mcp.provider.uri": "file:///path/to/servers/root/build/everything/index.js", | ||
} | ||
} | ||
``` | ||
1. Reload the VS Code window. You should see `servers/everything` in the `@`-mention dropdown. | ||
|
||
To hook up to the Postgres MCP provider, use: | ||
|
||
```json | ||
"openctx.providers": { | ||
// ...other providers... | ||
"https://openctx.org/npm/@openctx/provider-modelcontextprotocol": { | ||
"nodeCommand": "node", | ||
"mcp.provider.uri": "file:///path/to/servers/root/build/postgres/index.js", | ||
"mcp.provider.args": [ | ||
"postgresql://sourcegraph:sourcegraph@localhost:5432/sourcegraph" | ||
] | ||
} | ||
} | ||
``` | ||
|
||
## More MCP Servers | ||
|
||
The following MCP servers are available in the [modelcontextprotocol/servers](https://github.com/modelcontextprotocol/servers) repository: | ||
|
||
- [Brave Search](https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search) - Search the Brave search API | ||
- [Postgres](https://github.com/modelcontextprotocol/servers/tree/main/src/postgres) - Connect to your Postgres databases to query schema information and write optimized SQL | ||
- [Filesystem](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) - Access files on your local machine | ||
- [Everything](https://github.com/modelcontextprotocol/servers/tree/main/src/everything) - A demo server showing MCP capabilities | ||
- [Google Drive](https://github.com/modelcontextprotocol/servers/tree/main/src/gdrive) - Search and access your Google Drive documents | ||
- [Google Maps](https://github.com/modelcontextprotocol/servers/tree/main/src/google-maps) - Get directions and information about places | ||
- [Memo](https://github.com/modelcontextprotocol/servers/tree/main/src/memo) - Access your Memo notes | ||
- [Git](https://github.com/modelcontextprotocol/servers/tree/main/src/git) - Get git history and commit information | ||
- [Puppeteer](https://github.com/modelcontextprotocol/servers/tree/main/src/puppeteer) - Control headless Chrome for web automation | ||
- [SQLite](https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite) - Query SQLite databases | ||
|
||
## Creating your own MCP server | ||
|
||
See the [MCP docs](https://modelcontextprotocol.io) for how to create your own MCP servers. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import { basename } from 'node:path' | ||
import { Client } from '@modelcontextprotocol/sdk/client/index.js' | ||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' | ||
import { | ||
CallToolResultSchema, | ||
|
||
} from '@modelcontextprotocol/sdk/types.js' | ||
import type { | ||
Item, | ||
ItemsParams, | ||
ItemsResult, | ||
Mention, | ||
MentionsParams, | ||
MentionsResult, | ||
MetaParams, | ||
MetaResult, | ||
Provider, | ||
ProviderSettings, | ||
} from '@openctx/provider' | ||
const Ajv = require('ajv') | ||
|
||
async function createClient( | ||
nodeCommand: string, | ||
mcpProviderFile: string, | ||
mcpProviderArgs: string[], | ||
): Promise<Client> { | ||
const client = new Client( | ||
{ | ||
name: 'mcp-tool', | ||
version: '0.0.1', | ||
}, | ||
{ | ||
capabilities: { | ||
experimental: {}, | ||
sampling: {}, | ||
roots: {}, | ||
}, | ||
}, | ||
) | ||
const transport = new StdioClientTransport({ | ||
command: nodeCommand, | ||
args: [mcpProviderFile, ...mcpProviderArgs], | ||
}) | ||
await client.connect(transport) | ||
return client | ||
} | ||
|
||
class MCPToolsProxy implements Provider { | ||
private mcpClient?: Promise<Client> | ||
private toolSchemas: Map<string, any> = new Map() | ||
private ajv = new Ajv() | ||
|
||
// Gets the Metadata for the MCP Tools Provider | ||
async meta(_params: MetaParams, settings: ProviderSettings): Promise<MetaResult> { | ||
const nodeCommand: string = (settings.nodeCommand as string) ?? 'node' | ||
const mcpProviderUri = settings['mcp.provider.uri'] as string | ||
if (!mcpProviderUri) { | ||
this.mcpClient = undefined | ||
return { | ||
name: 'undefined MCP provider', | ||
} | ||
} | ||
if (!mcpProviderUri.startsWith('file://')) { | ||
throw new Error('mcp.provider.uri must be a file:// URI') | ||
} | ||
const mcpProviderFile = mcpProviderUri.slice('file://'.length) | ||
const mcpProviderArgsRaw = settings['mcp.provider.args'] | ||
const mcpProviderArgs = Array.isArray(mcpProviderArgsRaw) | ||
? mcpProviderArgsRaw.map(e => `${e}`) | ||
: [] | ||
this.mcpClient = createClient(nodeCommand, mcpProviderFile, mcpProviderArgs) | ||
const mcpClient = await this.mcpClient | ||
const serverInfo = mcpClient.getServerVersion() | ||
const name = serverInfo?.name ?? basename(mcpProviderFile) | ||
return { | ||
name, | ||
mentions: { | ||
label: name, | ||
}, | ||
} | ||
} | ||
|
||
// Gets Lists All the tools available in the MCP Provider along with their schemas | ||
async mentions?(params: MentionsParams, _settings: ProviderSettings): Promise<MentionsResult> { | ||
if (!this.mcpClient) { | ||
return [] | ||
} | ||
const mcpClient = await this.mcpClient | ||
const toolsResp = await mcpClient.listTools() | ||
|
||
const { tools } = toolsResp | ||
const mentions: Mention[] = [] | ||
for (const tool of tools) { | ||
// Store the schema in the Map using tool name as key | ||
this.toolSchemas.set(tool.name, JSON.stringify(tool.inputSchema)) | ||
|
||
const r = { | ||
uri: tool.uri, | ||
title: tool.name, | ||
description: tool.description, | ||
data: (tool.inputSchema), | ||
} as Mention | ||
mentions.push(r) | ||
} | ||
|
||
const query = params.query?.trim().toLowerCase() | ||
if (!query) { | ||
return mentions | ||
} | ||
const prefixMatches: Mention[] = [] | ||
const substringMatches: Mention[] = [] | ||
|
||
// Filters the tools based on the query | ||
for (const mention of mentions) { | ||
const title = mention.title.toLowerCase() | ||
if (title.startsWith(query)) { | ||
prefixMatches.push(mention) | ||
} else if (title.includes(query)) { | ||
substringMatches.push(mention) | ||
} | ||
} | ||
|
||
// Combines the prefix and substring matches | ||
return [...prefixMatches, ...substringMatches] | ||
} | ||
|
||
// Retrieves the schema for a tool from the Map using the tool name as key | ||
getToolSchema(toolName: string): any { | ||
return JSON.parse(this.toolSchemas.get(toolName) as string) | ||
} | ||
|
||
// Calls the tool with the provided input and returns the result | ||
async items?(params: ItemsParams, _settings: ProviderSettings): Promise<ItemsResult> { | ||
if (!this.mcpClient) { | ||
return [] | ||
} | ||
const mcpClient = await this.mcpClient | ||
|
||
const toolName = params.mention?.title | ||
const toolInput = params.mention?.data | ||
|
||
// Validates the tool input against the stored schema | ||
if (toolName && toolInput) { | ||
const schema = this.getToolSchema(toolName) | ||
if (schema) { | ||
const isValid = this.ajv.validate(schema, toolInput) | ||
if (!isValid) { | ||
console.error('Invalid tool input:', this.ajv.errors) | ||
throw new Error(`Invalid input for tool ${toolName}: ${JSON.stringify(this.ajv.errors)}`) | ||
} | ||
} | ||
} | ||
|
||
// Calls the tool with the provided input | ||
const response = await mcpClient.request( | ||
{ | ||
method: 'tools/call' as const, | ||
params: { | ||
name: toolName, | ||
arguments: toolInput | ||
}, | ||
}, | ||
CallToolResultSchema, | ||
) | ||
|
||
const contents = response.content | ||
const items: Item[] = [] | ||
for (const content of contents) { | ||
if (content.text) { | ||
items.push({ | ||
title: (toolName as string) ?? '', | ||
ai: { | ||
content: (content.text as string) ?? '', | ||
}, | ||
}) | ||
} else { | ||
console.log('No text field was present, mimeType was', content.mimeType) | ||
} | ||
} | ||
return items | ||
} | ||
|
||
dispose?(): void { | ||
if (this.mcpClient) { | ||
this.mcpClient.then(c => { | ||
c.close() | ||
}) | ||
} | ||
} | ||
} | ||
|
||
|
||
const proxy = new MCPToolsProxy() | ||
export default proxy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{ | ||
"name": "@openctx/provider-modelcontextprotocoltools", | ||
"version": "0.0.13", | ||
"description": "Use information from MCP providers", | ||
"license": "Apache-2.0", | ||
"homepage": "https://openctx.org/docs/providers/modelcontextprotocoltools", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/sourcegraph/openctx", | ||
"directory": "provider/modelcontextprotocoltools" | ||
}, | ||
"type": "module", | ||
"main": "dist/bundle.js", | ||
"types": "dist/index.d.ts", | ||
"files": [ | ||
"dist/bundle.js", | ||
"dist/index.d.ts" | ||
], | ||
"sideEffects": false, | ||
"scripts": { | ||
"bundle": "tsc --build && esbuild --log-level=error --platform=node --bundle --format=esm --outfile=dist/bundle.js index.ts", | ||
"prepublishOnly": "tsc --build --clean && npm run --silent bundle", | ||
"test": "vitest", | ||
"test:unit": "vitest run", | ||
"watch": "tsc --build --watch & esbuild --log-level=error --platform=node --bundle --format=esm --outfile=dist/bundle.js --watch index.ts" | ||
}, | ||
"dependencies": { | ||
"@apidevtools/json-schema-ref-parser": "^11.7.3", | ||
"@modelcontextprotocol/sdk": "1.0.1", | ||
"@openctx/provider": "workspace:*", | ||
"ajv": "^8.17.1", | ||
"express": "^4.21.1", | ||
"json-schema-to-zod": "^0.1.5", | ||
"zod": "^3.24.1", | ||
"zod-to-json-schema": "^3.24.1" | ||
}, | ||
"pnpm": { | ||
"peerDependencyRules": { | ||
"allowAny": [ | ||
"@apidevtools/json-schema-ref-parser" | ||
] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"extends": "../../.config/tsconfig.base.json", | ||
"compilerOptions": { | ||
"rootDir": ".", | ||
"outDir": "dist", | ||
"lib": ["ESNext"] | ||
}, | ||
"include": ["*.ts"], | ||
"exclude": ["dist", "vitest.config.ts"], | ||
"references": [{ "path": "../../lib/provider" }] | ||
} |