Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding mcp provider #213

Merged
merged 2 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 54 additions & 0 deletions provider/modelcontextprotocol/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# MCP proxy for OpenCtx

This is a context provider for [OpenCtx](https://openctx.org) that fetches contents from a [MCP](https://github.com/modelcontextprotocol/specification) 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/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/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:

- [Postgres](https://github.com/modelcontextprotocol/servers/tree/main/src/postgres) - Connect to your Postgres databases to query schema information and write optimized SQL
- [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
- [Giphy](https://github.com/modelcontextprotocol/servers/tree/main/src/giphy) - Search gifs
- [Git](https://github.com/modelcontextprotocol/servers/tree/main/src/git) - Get git history and commit information
- [HubSpot](https://github.com/modelcontextprotocol/servers/tree/main/src/hubspot) - Access your HubSpot CRM data
- [OSAScript](https://github.com/modelcontextprotocol/servers/tree/main/src/osascript) - Execute AppleScript commands on macOS
- [Puppeteer](https://github.com/modelcontextprotocol/servers/tree/main/src/puppeteer) - Control headless Chrome for web automation
- [Spotify](https://github.com/modelcontextprotocol/servers/tree/main/src/spotify) - Access Spotify music data and playlists

## Creating your own MCP server

See the [MCP docs](https://modelcontextprotocol.io) for how to create your own MCP servers.
175 changes: 175 additions & 0 deletions provider/modelcontextprotocol/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { basename } from 'node:path'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
import {
CreateMessageRequestSchema,
ListResourcesResultSchema,
ProgressNotificationSchema,
ReadResourceResultSchema,
} from '@modelcontextprotocol/sdk/types.js'
import type {
Item,
ItemsParams,
ItemsResult,
Mention,
MentionsParams,
MentionsResult,
MetaParams,
MetaResult,
Provider,
ProviderSettings,
} from '@openctx/provider'

async function createClient(
nodeCommand: string,
mcpProviderFile: string,
mcpProviderArgs: string[],
): Promise<Client> {
const client = new Client(
{
name: 'mcp-inspector',
version: '0.0.1',
},
{
capabilities: {
experimental: {},
sampling: {},
roots: {},
},
},
)
const transport = new StdioClientTransport({
command: nodeCommand,
args: [mcpProviderFile, ...mcpProviderArgs],
})
await client.connect(transport)
console.log('connected to MCP server')

client.setNotificationHandler(ProgressNotificationSchema, notification => {
console.log('got MCP notif', notification)
})

client.setRequestHandler(CreateMessageRequestSchema, request => {
console.log('got MCP request', request)
return { _meta: {} }
})
return client
}

class MCPProxy implements Provider {
private mcpClient?: Promise<Client>

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,
},
}
}

async mentions?(params: MentionsParams, _settings: ProviderSettings): Promise<MentionsResult> {
if (!this.mcpClient) {
return []
}
const mcpClient = await this.mcpClient
const resourcesResp = await mcpClient.request(
{
method: 'resources/list',
params: {},
},
ListResourcesResultSchema,
)

const { resources } = resourcesResp
const mentions: Mention[] = []
for (const resource of resources) {
const r = {
uri: resource.uri,
title: resource.name,
description: resource.description,
}
mentions.push(r)
}

const query = params.query?.trim().toLowerCase()
if (!query) {
return mentions
}
const prefixMatches: Mention[] = []
const substringMatches: Mention[] = []

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)
}
}

return [...prefixMatches, ...substringMatches]
}

async items?(params: ItemsParams, _settings: ProviderSettings): Promise<ItemsResult> {
if (!params.mention || !this.mcpClient) {
return []
}
const mcpClient = await this.mcpClient
const response = await mcpClient.request(
{
method: 'resources/read' as const,
params: { uri: params.mention.uri },
},
ReadResourceResultSchema,
)

const { contents } = response

const items: Item[] = []
for (const content of contents) {
if (content.text) {
items.push({
title: content.uri,
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 MCPProxy()
export default proxy
30 changes: 30 additions & 0 deletions provider/modelcontextprotocol/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@openctx/provider-mcp",
"version": "0.0.11",
"description": "Use information from MCP providers",
"license": "Apache-2.0",
"homepage": "https://openctx.org/docs/providers/mcp",
"repository": {
"type": "git",
"url": "https://github.com/sourcegraph/openctx",
"directory": "provider/mcp"
},
"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",
"watch": "tsc --build --watch & esbuild --log-level=error --platform=node --bundle --format=esm --outfile=dist/bundle.js --watch index.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^0.7.0",
"@openctx/provider": "workspace:*"
}
}
11 changes: 11 additions & 0 deletions provider/modelcontextprotocol/tsconfig.json
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" }]
}
3 changes: 3 additions & 0 deletions provider/modelcontextprotocol/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({})
8 changes: 8 additions & 0 deletions web/content/docs/providers/modelcontextprotocol.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const info = {
title: 'Anthropic Model Context Protocol',
group: 'providers',
}

import Readme from '../../../../provider/modelcontextprotocol/README.md'

<Readme />
Loading