export declare const mcpRoutesTemplate = "import { NextResponse } from \"next/server\";\nimport { WebStandardStreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js\";\nimport {\n createMCPServer,\n searchDocs,\n getIndexStatus,\n DOCS_TOOLS,\n listDocs,\n getDoc,\n MCP_MAX_REQUEST_BYTES,\n MCP_MAX_TOOL_ARGUMENT_BYTES,\n searchDocsArgsSchema,\n getDocArgsSchema,\n listDocsArgsSchema,\n serializeMCPResult,\n} from \"@/services/mcp\";\nimport type { MCPToolName } from \"@/services/mcp\";\nimport { rateLimit } from \"@/utils/rateLimit\";\nimport { isMcpRequestAuthorized } from \"@/lib/access\";\nimport { readJsonBody, RequestTooLargeError } from \"@/utils/requestBody\";\n\nconst MAX_PROTOCOL_MESSAGES = 20;\n\nfunction isRecord(value: unknown): value is Record {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction jsonByteLength(value: unknown): number {\n return new TextEncoder().encode(JSON.stringify(value) ?? \"null\").byteLength;\n}\n\nfunction protocolToolCallError(body: unknown): string | null {\n const messages = Array.isArray(body) ? body : [body];\n if (messages.length === 0 || messages.length > MAX_PROTOCOL_MESSAGES) {\n return \"Invalid protocol batch\";\n }\n\n let toolCallCount = 0;\n for (const message of messages) {\n if (!isRecord(message) || message.method !== \"tools/call\") continue;\n toolCallCount++;\n if (toolCallCount > 1) {\n return \"Only one tools/call is allowed per request\";\n }\n if (!isRecord(message.params) || typeof message.params.name !== \"string\") {\n return \"Invalid tool call\";\n }\n\n const args = message.params.arguments ?? {};\n if (jsonByteLength(args) > MCP_MAX_TOOL_ARGUMENT_BYTES) {\n return \"Tool arguments exceed the maximum size\";\n }\n\n let valid = true;\n switch (message.params.name) {\n case \"search_docs\":\n valid = searchDocsArgsSchema.safeParse(args).success;\n break;\n case \"get_doc\":\n valid = getDocArgsSchema.safeParse(args).success;\n break;\n case \"list_docs\":\n valid = listDocsArgsSchema.safeParse(args).success;\n break;\n }\n if (!valid) return \"Invalid tool arguments\";\n }\n return null;\n}\n\nfunction protocolError(message: string, status = 400): Response {\n return Response.json(\n {\n jsonrpc: \"2.0\",\n error: { code: status === 500 ? -32603 : -32602, message },\n id: null,\n },\n { status },\n );\n}\n\nfunction createTransport() {\n return new WebStandardStreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: true,\n });\n}\n\nasync function handleMCPRequest(req: Request, parsedBody?: unknown) {\n const transport = createTransport();\n const server = createMCPServer();\n let closed = false;\n const close = () => {\n if (closed) return;\n closed = true;\n void Promise.allSettled([transport.close(), server.close()]);\n };\n req.signal.addEventListener(\"abort\", close, { once: true });\n\n try {\n await server.connect(transport);\n const response = await transport.handleRequest(\n req,\n parsedBody === undefined ? undefined : { parsedBody },\n );\n\n const body = response.clone().body;\n if (body) {\n void body\n .pipeTo(new WritableStream())\n .catch(() => {})\n .finally(close);\n } else {\n close();\n }\n return response;\n } catch (error: unknown) {\n console.error(\"[doccupine] MCP protocol request failed:\", error);\n close();\n return protocolError(\"Internal server error\", 500);\n }\n}\n\ninterface ToolCallRequest {\n tool: MCPToolName;\n params: unknown;\n}\n\nfunction toolResponse(value: unknown): Response {\n const result = serializeMCPResult(value);\n if (result.tooLarge) {\n return NextResponse.json(\n { error: \"Tool result exceeds the maximum response size\" },\n { status: 413 },\n );\n }\n return new Response(result.text, {\n headers: {\n \"Content-Type\": \"application/json; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n },\n });\n}\n\nasync function handleRESTRequest(req: Request, body: ToolCallRequest) {\n try {\n const { tool, params } = body;\n\n switch (tool) {\n case \"search_docs\": {\n const parsed = searchDocsArgsSchema.safeParse(params);\n if (!parsed.success) {\n return NextResponse.json(\n { error: \"Invalid params\" },\n { status: 400 },\n );\n }\n const results = await searchDocs(\n parsed.data.query,\n parsed.data.limit ?? 6,\n req.signal,\n );\n return toolResponse({\n content: results.map(({ chunk, score }) => ({\n path: chunk.path,\n uri: chunk.uri,\n score: score.toFixed(3),\n text: chunk.text,\n })),\n });\n }\n\n case \"get_doc\": {\n const parsed = getDocArgsSchema.safeParse(params);\n if (!parsed.success) {\n return NextResponse.json(\n { error: \"Invalid params\" },\n { status: 400 },\n );\n }\n req.signal.throwIfAborted();\n const doc = await getDoc({ path: parsed.data.path });\n req.signal.throwIfAborted();\n if (!doc) {\n return NextResponse.json(\n { error: \"Document not found\" },\n { status: 404 },\n );\n }\n return toolResponse({ content: doc });\n }\n\n case \"list_docs\": {\n const parsed = listDocsArgsSchema.safeParse(params);\n if (!parsed.success) {\n return NextResponse.json(\n { error: \"Invalid params\" },\n { status: 400 },\n );\n }\n req.signal.throwIfAborted();\n const docs = await listDocs({ directory: parsed.data.directory });\n req.signal.throwIfAborted();\n return toolResponse({\n content: docs.map((doc) => ({\n name: doc.name,\n path: doc.path,\n uri: doc.uri,\n })),\n });\n }\n\n default:\n return NextResponse.json({ error: \"Unknown tool\" }, { status: 400 });\n }\n } catch (error: unknown) {\n console.error(\"[doccupine] MCP REST tool failed:\", error);\n return NextResponse.json(\n { error: \"Internal server error\" },\n { status: 500 },\n );\n }\n}\n\nexport async function POST(req: Request) {\n if (!(await isMcpRequestAuthorized(req))) {\n return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 });\n }\n\n const { allowed, retryAfter } = rateLimit(req);\n if (!allowed) {\n return NextResponse.json(\n { error: \"Too many requests\" },\n { status: 429, headers: { \"Retry-After\": String(retryAfter) } },\n );\n }\n\n let body: unknown;\n try {\n body = await readJsonBody(req, MCP_MAX_REQUEST_BYTES);\n } catch (error: unknown) {\n if (req.signal.aborted) throw error;\n if (error instanceof RequestTooLargeError) {\n return NextResponse.json(\n { error: \"Request body too large\" },\n { status: 413 },\n );\n }\n return NextResponse.json({ error: \"Invalid JSON body\" }, { status: 400 });\n }\n\n if (Array.isArray(body) || (isRecord(body) && \"jsonrpc\" in body)) {\n const validationError = protocolToolCallError(body);\n if (validationError) return protocolError(validationError);\n return handleMCPRequest(req, body);\n }\n\n if (isRecord(body) && typeof body.tool === \"string\") {\n if (jsonByteLength(body.params ?? {}) > MCP_MAX_TOOL_ARGUMENT_BYTES) {\n return NextResponse.json(\n { error: \"Tool arguments exceed the maximum size\" },\n { status: 413 },\n );\n }\n return handleRESTRequest(req, {\n tool: body.tool as MCPToolName,\n params: body.params ?? {},\n });\n }\n\n return NextResponse.json(\n { error: \"Invalid request format\" },\n { status: 400 },\n );\n}\n\nexport async function GET(req: Request) {\n if (!(await isMcpRequestAuthorized(req))) {\n return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 });\n }\n\n const status = getIndexStatus();\n return NextResponse.json({\n tools: DOCS_TOOLS,\n index: { ready: status.ready, chunkCount: status.chunkCount },\n });\n}\n\nexport async function DELETE(req: Request) {\n if (!(await isMcpRequestAuthorized(req))) {\n return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 });\n }\n return handleMCPRequest(req);\n}\n";