source dump of claude code
at main 196 lines 6.3 kB view raw
1import { Server } from '@modelcontextprotocol/sdk/server/index.js' 2import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' 3import { 4 CallToolRequestSchema, 5 type CallToolResult, 6 ListToolsRequestSchema, 7 type ListToolsResult, 8 type Tool, 9} from '@modelcontextprotocol/sdk/types.js' 10import { getDefaultAppState } from 'src/state/AppStateStore.js' 11import review from '../commands/review.js' 12import type { Command } from '../commands.js' 13import { 14 findToolByName, 15 getEmptyToolPermissionContext, 16 type ToolUseContext, 17} from '../Tool.js' 18import { getTools } from '../tools.js' 19import { createAbortController } from '../utils/abortController.js' 20import { createFileStateCacheWithSizeLimit } from '../utils/fileStateCache.js' 21import { logError } from '../utils/log.js' 22import { createAssistantMessage } from '../utils/messages.js' 23import { getMainLoopModel } from '../utils/model/model.js' 24import { hasPermissionsToUseTool } from '../utils/permissions/permissions.js' 25import { setCwd } from '../utils/Shell.js' 26import { jsonStringify } from '../utils/slowOperations.js' 27import { getErrorParts } from '../utils/toolErrors.js' 28import { zodToJsonSchema } from '../utils/zodToJsonSchema.js' 29 30type ToolInput = Tool['inputSchema'] 31type ToolOutput = Tool['outputSchema'] 32 33const MCP_COMMANDS: Command[] = [review] 34 35export async function startMCPServer( 36 cwd: string, 37 debug: boolean, 38 verbose: boolean, 39): Promise<void> { 40 // Use size-limited LRU cache for readFileState to prevent unbounded memory growth 41 // 100 files and 25MB limit should be sufficient for MCP server operations 42 const READ_FILE_STATE_CACHE_SIZE = 100 43 const readFileStateCache = createFileStateCacheWithSizeLimit( 44 READ_FILE_STATE_CACHE_SIZE, 45 ) 46 setCwd(cwd) 47 const server = new Server( 48 { 49 name: 'claude/tengu', 50 version: MACRO.VERSION, 51 }, 52 { 53 capabilities: { 54 tools: {}, 55 }, 56 }, 57 ) 58 59 server.setRequestHandler( 60 ListToolsRequestSchema, 61 async (): Promise<ListToolsResult> => { 62 // TODO: Also re-expose any MCP tools 63 const toolPermissionContext = getEmptyToolPermissionContext() 64 const tools = getTools(toolPermissionContext) 65 return { 66 tools: await Promise.all( 67 tools.map(async tool => { 68 let outputSchema: ToolOutput | undefined 69 if (tool.outputSchema) { 70 const convertedSchema = zodToJsonSchema(tool.outputSchema) 71 // MCP SDK requires outputSchema to have type: "object" at root level 72 // Skip schemas with anyOf/oneOf at root (from z.union, z.discriminatedUnion, etc.) 73 // See: https://github.com/anthropics/claude-code/issues/8014 74 if ( 75 typeof convertedSchema === 'object' && 76 convertedSchema !== null && 77 'type' in convertedSchema && 78 convertedSchema.type === 'object' 79 ) { 80 outputSchema = convertedSchema as ToolOutput 81 } 82 } 83 return { 84 ...tool, 85 description: await tool.prompt({ 86 getToolPermissionContext: async () => toolPermissionContext, 87 tools, 88 agents: [], 89 }), 90 inputSchema: zodToJsonSchema(tool.inputSchema) as ToolInput, 91 outputSchema, 92 } 93 }), 94 ), 95 } 96 }, 97 ) 98 99 server.setRequestHandler( 100 CallToolRequestSchema, 101 async ({ params: { name, arguments: args } }): Promise<CallToolResult> => { 102 const toolPermissionContext = getEmptyToolPermissionContext() 103 // TODO: Also re-expose any MCP tools 104 const tools = getTools(toolPermissionContext) 105 const tool = findToolByName(tools, name) 106 if (!tool) { 107 throw new Error(`Tool ${name} not found`) 108 } 109 110 // Assume MCP servers do not read messages separately from the tool 111 // call arguments. 112 const toolUseContext: ToolUseContext = { 113 abortController: createAbortController(), 114 options: { 115 commands: MCP_COMMANDS, 116 tools, 117 mainLoopModel: getMainLoopModel(), 118 thinkingConfig: { type: 'disabled' }, 119 mcpClients: [], 120 mcpResources: {}, 121 isNonInteractiveSession: true, 122 debug, 123 verbose, 124 agentDefinitions: { activeAgents: [], allAgents: [] }, 125 }, 126 getAppState: () => getDefaultAppState(), 127 setAppState: () => {}, 128 messages: [], 129 readFileState: readFileStateCache, 130 setInProgressToolUseIDs: () => {}, 131 setResponseLength: () => {}, 132 updateFileHistoryState: () => {}, 133 updateAttributionState: () => {}, 134 } 135 136 // TODO: validate input types with zod 137 try { 138 if (!tool.isEnabled()) { 139 throw new Error(`Tool ${name} is not enabled`) 140 } 141 const validationResult = await tool.validateInput?.( 142 (args as never) ?? {}, 143 toolUseContext, 144 ) 145 if (validationResult && !validationResult.result) { 146 throw new Error( 147 `Tool ${name} input is invalid: ${validationResult.message}`, 148 ) 149 } 150 const finalResult = await tool.call( 151 (args ?? {}) as never, 152 toolUseContext, 153 hasPermissionsToUseTool, 154 createAssistantMessage({ 155 content: [], 156 }), 157 ) 158 159 return { 160 content: [ 161 { 162 type: 'text' as const, 163 text: 164 typeof finalResult === 'string' 165 ? finalResult 166 : jsonStringify(finalResult.data), 167 }, 168 ], 169 } 170 } catch (error) { 171 logError(error) 172 173 const parts = 174 error instanceof Error ? getErrorParts(error) : [String(error)] 175 const errorText = parts.filter(Boolean).join('\n').trim() || 'Error' 176 177 return { 178 isError: true, 179 content: [ 180 { 181 type: 'text', 182 text: errorText, 183 }, 184 ], 185 } 186 } 187 }, 188 ) 189 190 async function runServer() { 191 const transport = new StdioServerTransport() 192 await server.connect(transport) 193 } 194 195 return await runServer() 196}