Skip to content

Commit

Permalink
mcp
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-sev committed Nov 25, 2024
1 parent 40331bd commit 68b0ef0
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 0 deletions.
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 />

0 comments on commit 68b0ef0

Please sign in to comment.