π File detail
entrypoints/mcp.ts
π§© .tsπ 197 linesπΎ 6,270 bytesπ text
β Back to All Filesπ― Use case
This file lives under βentrypoints/β, which covers public entry surfaces and SDK-facing entrypoints. On the API surface it exposes startMCPServer β mainly functions, hooks, or classes. Dependencies touch @modelcontextprotocol and src. It composes internal code from commands, Tool, tools, and utils (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { CallToolRequestSchema, type CallToolResult,
π€ Exports (heuristic)
startMCPServer
π External import roots
Package roots from from "β¦" (relative paths omitted).
@modelcontextprotocolsrc
π₯οΈ Source preview
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()
}