feat: mcp (#31)

* feat: add mcp connections table and related crud api

* feat: mcp-stdio api
This commit is contained in:
Acbox Liu
2026-02-09 20:07:40 +08:00
committed by GitHub
parent 92838ef8da
commit 8ea779779e
17 changed files with 2486 additions and 32 deletions
+14 -3
View File
@@ -1,5 +1,5 @@
import { generateText, ImagePart, LanguageModelUsage, ModelMessage, stepCountIs, streamText, UserModelMessage } from 'ai'
import { AgentInput, AgentParams, AgentSkill, allActions, HTTPMCPConnection, MCPConnection, Schedule } from './types'
import { AgentInput, AgentParams, AgentSkill, allActions, HTTPMCPConnection, MCPConnection, Schedule, StdioMCPConnection } from './types'
import { system, schedule, user, subagentSystem } from './prompts'
import { AuthFetcher } from './index'
import { createModel } from './model'
@@ -56,7 +56,14 @@ export const createAgent = ({
'Authorization': `Bearer ${auth.bearer}`,
},
}
return [fs]
const mcpFetch: StdioMCPConnection = {
type: 'stdio',
name: 'mcp-fetch',
command: 'npx',
args: ['fetch-mcp'],
env: {},
}
return [fs, mcpFetch]
}
const generateSystemPrompt = () => {
@@ -82,7 +89,11 @@ export const createAgent = ({
const { tools: mcpTools, close: closeMCP } = await getMCPTools([
...defaultMCPConnections,
...mcpConnections,
])
], {
botId: identity.botId,
auth,
fetch,
})
Object.assign(tools, mcpTools)
return {
tools,
+55 -7
View File
@@ -1,7 +1,15 @@
import { HTTPMCPConnection, MCPConnection, SSEMCPConnection, StdioMCPConnection } from '../types'
import { createMCPClient } from '@ai-sdk/mcp'
import { AuthFetcher } from '../index'
import type { AgentAuthContext } from '../types/agent'
export const getMCPTools = async (connections: MCPConnection[]) => {
type MCPToolOptions = {
botId?: string
auth?: AgentAuthContext
fetch?: AuthFetcher
}
export const getMCPTools = async (connections: MCPConnection[], options: MCPToolOptions = {}) => {
const closeCallbacks: Array<() => Promise<void>> = []
const getHTTPTools = async (connection: HTTPMCPConnection) => {
@@ -13,7 +21,8 @@ export const getMCPTools = async (connections: MCPConnection[]) => {
}
})
closeCallbacks.push(() => client.close())
return await client.tools()
const tools = await client.tools()
return tools
}
const getSSETools = async (connection: SSEMCPConnection) => {
@@ -25,15 +34,51 @@ export const getMCPTools = async (connections: MCPConnection[]) => {
}
})
closeCallbacks.push(() => client.close())
return await client.tools()
const tools = await client.tools()
return tools
}
const getStdioTools = async (connection: StdioMCPConnection) => {
// TODO: Implement stdio tools
return {}
if (!options.fetch || !options.botId || !options.auth) {
throw new Error('stdio mcp requires auth fetcher and bot id')
}
const response = await options.fetch(`/bots/${options.botId}/mcp-stdio`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: connection.name,
command: connection.command,
args: connection.args ?? [],
env: connection.env ?? {},
cwd: connection.cwd ?? ''
})
})
if (!response.ok) {
const text = await response.text().catch(() => '')
throw new Error(`mcp-stdio failed: ${response.status} ${text}`)
}
const data = await response.json().catch(() => ({} as { url?: string }))
const rawUrl = typeof data?.url === 'string' ? data.url : ''
if (!rawUrl) {
throw new Error('mcp-stdio response missing url')
}
const baseUrl = options.auth.baseUrl ?? ''
const url = rawUrl.startsWith('http')
? rawUrl
: `${baseUrl.replace(/\/$/, '')}/${rawUrl.replace(/^\//, '')}`
return await getHTTPTools({
type: 'http',
name: connection.name,
url,
headers: {
'Authorization': `Bearer ${options.auth.bearer}`
}
})
}
const toolSets = await Promise.all(connections.map(connection => {
const toolSets = await Promise.all(connections.map(async (connection) => {
switch (connection.type) {
case 'http':
return getHTTPTools(connection)
@@ -41,6 +86,9 @@ export const getMCPTools = async (connections: MCPConnection[]) => {
return getSSETools(connection)
case 'stdio':
return getStdioTools(connection)
default:
console.warn('unknown mcp connection type', connection)
return {}
}
}))
@@ -50,4 +98,4 @@ export const getMCPTools = async (connections: MCPConnection[]) => {
await Promise.all(closeCallbacks.map(callback => callback()))
}
}
}
}