init claude-code
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user