Last active
December 13, 2024 21:48
-
-
Save ewired/9d6a4c32f58c3ec2052d47736dc07e22 to your computer and use it in GitHub Desktop.
OpenRouter MCP Server
This file contains hidden or 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
#!/usr/bin/env -S deno run --allow-net --allow-env | |
/* | |
{ | |
"mcpServers": { | |
"openrouter": { | |
"command": "/path/to/deno", | |
"args": [ | |
"run", | |
"--allow-net", | |
"--allow-env", | |
"/path/to/openrouter.ts", | |
"perplexity/llama-3.1-sonar-huge-128k-online" | |
], | |
"env": { | |
"OPENROUTER_API_KEY": "<KEY HERE>", | |
"TOOL_DESCRIPTION": "Query the Perplexity online model" | |
} | |
} | |
} | |
} | |
*/ | |
import { Server } from "npm:@modelcontextprotocol/sdk/server/index.js"; | |
import { StdioServerTransport } from "npm:@modelcontextprotocol/sdk/server/stdio.js"; | |
import { | |
CallToolRequestSchema, | |
ErrorCode, | |
ListToolsRequestSchema, | |
McpError, | |
} from "npm:@modelcontextprotocol/sdk/types.js"; | |
interface QueryArgs { | |
query: string; | |
} | |
const isValidQueryArgs = (args: unknown): args is QueryArgs => { | |
if (typeof args !== "object" || args === null) return false; | |
const { query } = args as QueryArgs; | |
return typeof query === "string"; | |
}; | |
const TOOL_DESCRIPTION = Deno.env.get("TOOL_DESCRIPTION"); | |
class OpenRouterServer { | |
private server: Server; | |
private model: string; | |
private apiKey: string; | |
constructor(model: string) { | |
this.model = model; | |
const apiKey = Deno.env.get("OPENROUTER_API_KEY"); | |
if (!apiKey) { | |
throw new Error("OPENROUTER_API_KEY environment variable is required"); | |
} | |
this.apiKey = apiKey; | |
this.server = new Server( | |
{ | |
name: "openrouter-server", | |
version: "0.1.0", | |
}, | |
{ | |
capabilities: { | |
tools: {}, | |
}, | |
}, | |
); | |
this.setupToolHandlers(); | |
this.server.onerror = (error) => console.error("[MCP Error]", error); | |
// Handle graceful shutdown | |
const shutdown = async () => { | |
await this.server.close(); | |
Deno.exit(0); | |
}; | |
Deno.addSignalListener("SIGINT", shutdown); | |
Deno.addSignalListener("SIGTERM", shutdown); | |
} | |
private setupToolHandlers() { | |
this.server.setRequestHandler( | |
ListToolsRequestSchema, | |
() => | |
Promise.resolve({ | |
tools: [ | |
{ | |
name: "query", | |
description: TOOL_DESCRIPTION ?? | |
"Query an AI model through OpenRouter", | |
inputSchema: { | |
type: "object", | |
properties: { | |
query: { | |
type: "string", | |
description: "The query to send to the model", | |
}, | |
}, | |
required: ["query"], | |
}, | |
}, | |
], | |
}), | |
); | |
this.server.setRequestHandler(CallToolRequestSchema, async (request) => { | |
if (request.params.name !== "query") { | |
throw new McpError( | |
ErrorCode.MethodNotFound, | |
`Unknown tool: ${request.params.name}`, | |
); | |
} | |
if (!isValidQueryArgs(request.params.arguments)) { | |
throw new McpError( | |
ErrorCode.InvalidParams, | |
"Invalid query arguments", | |
); | |
} | |
const { query } = request.params.arguments; | |
try { | |
const response = await fetch( | |
"https://openrouter.ai/api/v1/chat/completions", | |
{ | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
"Authorization": `Bearer ${this.apiKey}`, | |
}, | |
body: JSON.stringify({ | |
model: this.model, | |
messages: [ | |
{ | |
role: "user", | |
content: query, | |
}, | |
], | |
}), | |
}, | |
); | |
if (!response.ok) { | |
const error = await response.text(); | |
throw new Error(`OpenRouter API error: ${error}`); | |
} | |
const data = await response.json(); | |
const answer = data.choices[0]?.message?.content; | |
if (!answer) { | |
throw new Error("No response from model"); | |
} | |
return { | |
content: [ | |
{ | |
type: "text", | |
text: answer, | |
}, | |
], | |
}; | |
} catch (error) { | |
const message = error instanceof Error ? error.message : String(error); | |
return { | |
content: [ | |
{ | |
type: "text", | |
text: `Query failed: ${message}`, | |
}, | |
], | |
isError: true, | |
}; | |
} | |
}); | |
} | |
async run() { | |
const transport = new StdioServerTransport(); | |
await this.server.connect(transport); | |
console.error( | |
`OpenRouter MCP server running on stdio (model: ${this.model})`, | |
); | |
} | |
} | |
// Parse arguments | |
const [model = "perplexity/llama-3.1-sonar-huge-128k-online"] = Deno.args; | |
const server = new OpenRouterServer(model); | |
server.run().catch(console.error); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment