Download src/entrypoints/mcp.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 6.27 kB
-
https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/entrypoints/mcp.ts
- Command line
-
hf download hf://chenbhao/codev@afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/entrypoints/mcp.ts
-
curl -L -o mcp.ts https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/entrypoints/mcp.ts
6.27 kB
| import { Server } from '@modelcontextprotocol/sdk/server/index.js' | |
| import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' | |
| import { | |
| CallToolRequestSchema, | |
| type CallToolResult, | |
| ListToolsRequestSchema, | |
| type ListToolsResult, | |
| type Tool, | |
| } from '@modelcontextprotocol/sdk/types.js' | |
| import { getDefaultAppState } from 'src/state/AppStateStore.js' | |
| import review from '../commands/review.js' | |
| import type { Command } from '../commands.js' | |
| import { | |
| findToolByName, | |
| getEmptyToolPermissionContext, | |
| type ToolUseContext, | |
| } from '../Tool.js' | |
| import { getTools } from '../tools.js' | |
| import { createAbortController } from '../utils/abortController.js' | |
| import { createFileStateCacheWithSizeLimit } from '../utils/fileStateCache.js' | |
| import { logError } from '../utils/log.js' | |
| import { createAssistantMessage } from '../utils/messages.js' | |
| import { getMainLoopModel } from '../utils/model/model.js' | |
| import { hasPermissionsToUseTool } from '../utils/permissions/permissions.js' | |
| import { setCwd } from '../utils/Shell.js' | |
| import { jsonStringify } from '../utils/slowOperations.js' | |
| import { getErrorParts } from '../utils/toolErrors.js' | |
| import { zodToJsonSchema } from '../utils/zodToJsonSchema.js' | |
| type ToolInput = Tool['inputSchema'] | |
| type ToolOutput = Tool['outputSchema'] | |
| const MCP_COMMANDS: Command[] = [review] | |
| export async function startMCPServer( | |
| cwd: string, | |
| debug: boolean, | |
| verbose: boolean, | |
| ): Promise<void> { | |
| // Use size-limited LRU cache for readFileState to prevent unbounded memory growth | |
| // 100 files and 25MB limit should be sufficient for MCP server operations | |
| const READ_FILE_STATE_CACHE_SIZE = 100 | |
| const readFileStateCache = createFileStateCacheWithSizeLimit( | |
| READ_FILE_STATE_CACHE_SIZE, | |
| ) | |
| setCwd(cwd) | |
| const server = new Server( | |
| { | |
| name: 'claude/tengu', | |
| version: MACRO.VERSION, | |
| }, | |
| { | |
| capabilities: { | |
| tools: {}, | |
| }, | |
| }, | |
| ) | |
| server.setRequestHandler( | |
| ListToolsRequestSchema, | |
| async (): Promise<ListToolsResult> => { | |
| // TODO: Also re-expose any MCP tools | |
| const toolPermissionContext = getEmptyToolPermissionContext() | |
| const tools = getTools(toolPermissionContext) | |
| return { | |
| tools: await Promise.all( | |
| tools.map(async tool => { | |
| let outputSchema: ToolOutput | undefined | |
| if (tool.outputSchema) { | |
| const convertedSchema = zodToJsonSchema(tool.outputSchema) | |
| // MCP SDK requires outputSchema to have type: "object" at root level | |
| // Skip schemas with anyOf/oneOf at root (from z.union, z.discriminatedUnion, etc.) | |
| // See: https://github.com/anthropics/claude-code/issues/8014 | |
| if ( | |
| typeof convertedSchema === 'object' && | |
| convertedSchema !== null && | |
| 'type' in convertedSchema && | |
| convertedSchema.type === 'object' | |
| ) { | |
| outputSchema = convertedSchema as ToolOutput | |
| } | |
| } | |
| return { | |
| ...tool, | |
| description: await tool.prompt({ | |
| getToolPermissionContext: async () => toolPermissionContext, | |
| tools, | |
| agents: [], | |
| }), | |
| inputSchema: zodToJsonSchema(tool.inputSchema) as ToolInput, | |
| outputSchema, | |
| } | |
| }), | |
| ), | |
| } | |
| }, | |
| ) | |
| server.setRequestHandler( | |
| CallToolRequestSchema, | |
| async ({ params: { name, arguments: args } }): Promise<CallToolResult> => { | |
| const toolPermissionContext = getEmptyToolPermissionContext() | |
| // TODO: Also re-expose any MCP tools | |
| const tools = getTools(toolPermissionContext) | |
| const tool = findToolByName(tools, name) | |
| if (!tool) { | |
| throw new Error(`Tool ${name} not found`) | |
| } | |
| // Assume MCP servers do not read messages separately from the tool | |
| // call arguments. | |
| const toolUseContext: ToolUseContext = { | |
| abortController: createAbortController(), | |
| options: { | |
| commands: MCP_COMMANDS, | |
| tools, | |
| mainLoopModel: getMainLoopModel(), | |
| thinkingConfig: { type: 'disabled' }, | |
| mcpClients: [], | |
| mcpResources: {}, | |
| isNonInteractiveSession: true, | |
| debug, | |
| verbose, | |
| agentDefinitions: { activeAgents: [], allAgents: [] }, | |
| }, | |
| getAppState: () => getDefaultAppState(), | |
| setAppState: () => {}, | |
| messages: [], | |
| readFileState: readFileStateCache, | |
| setInProgressToolUseIDs: () => {}, | |
| setResponseLength: () => {}, | |
| updateFileHistoryState: () => {}, | |
| updateAttributionState: () => {}, | |
| } | |
| // TODO: validate input types with zod | |
| try { | |
| if (!tool.isEnabled()) { | |
| throw new Error(`Tool ${name} is not enabled`) | |
| } | |
| const validationResult = await tool.validateInput?.( | |
| (args as never) ?? {}, | |
| toolUseContext, | |
| ) | |
| if (validationResult && !validationResult.result) { | |
| throw new Error( | |
| `Tool ${name} input is invalid: ${validationResult.message}`, | |
| ) | |
| } | |
| const finalResult = await tool.call( | |
| (args ?? {}) as never, | |
| toolUseContext, | |
| hasPermissionsToUseTool, | |
| createAssistantMessage({ | |
| content: [], | |
| }), | |
| ) | |
| return { | |
| content: [ | |
| { | |
| type: 'text' as const, | |
| text: | |
| typeof finalResult === 'string' | |
| ? finalResult | |
| : jsonStringify(finalResult.data), | |
| }, | |
| ], | |
| } | |
| } catch (error) { | |
| logError(error) | |
| const parts = | |
| error instanceof Error ? getErrorParts(error) : [String(error)] | |
| const errorText = parts.filter(Boolean).join('\n').trim() || 'Error' | |
| return { | |
| isError: true, | |
| content: [ | |
| { | |
| type: 'text', | |
| text: errorText, | |
| }, | |
| ], | |
| } | |
| } | |
| }, | |
| ) | |
| async function runServer() { | |
| const transport = new StdioServerTransport() | |
| await server.connect(transport) | |
| } | |
| return await runServer() | |
| } | |