Files
ClosedCode/packages/opencode/src/server/routes/instance/httpapi/mcp.ts
T

107 lines
3.5 KiB
TypeScript

import { MCP } from "@/mcp"
import { ConfigMCP } from "@/config/mcp"
import { Effect, Layer, Schema } from "effect"
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "./auth"
const AddPayload = Schema.Struct({
name: Schema.String,
config: ConfigMCP.Info,
}).annotate({ identifier: "McpAddInput" })
const StatusMap = Schema.Record(Schema.String, MCP.Status)
export const McpPaths = {
status: "/mcp",
connect: "/mcp/:name/connect",
disconnect: "/mcp/:name/disconnect",
} as const
export const McpApi = HttpApi.make("mcp")
.add(
HttpApiGroup.make("mcp")
.add(
HttpApiEndpoint.get("status", McpPaths.status, {
success: Schema.Record(Schema.String, MCP.Status),
}).annotateMerge(
OpenApi.annotations({
identifier: "mcp.status",
summary: "Get MCP status",
description: "Get the status of all Model Context Protocol (MCP) servers.",
}),
),
HttpApiEndpoint.post("add", McpPaths.status, {
payload: AddPayload,
success: StatusMap,
}).annotateMerge(
OpenApi.annotations({
identifier: "mcp.add",
summary: "Add MCP server",
description: "Dynamically add a new Model Context Protocol (MCP) server to the system.",
}),
),
HttpApiEndpoint.post("connect", McpPaths.connect, {
params: { name: Schema.String },
success: Schema.Boolean,
}).annotateMerge(
OpenApi.annotations({
identifier: "mcp.connect",
description: "Connect an MCP server.",
}),
),
HttpApiEndpoint.post("disconnect", McpPaths.disconnect, {
params: { name: Schema.String },
success: Schema.Boolean,
}).annotateMerge(
OpenApi.annotations({
identifier: "mcp.disconnect",
description: "Disconnect an MCP server.",
}),
),
)
.annotateMerge(
OpenApi.annotations({
title: "mcp",
description: "Experimental HttpApi MCP routes.",
}),
)
.middleware(Authorization),
)
.annotateMerge(
OpenApi.annotations({
title: "opencode experimental HttpApi",
version: "0.0.1",
description: "Experimental HttpApi surface for selected instance routes.",
}),
)
export const mcpHandlers = Layer.unwrap(
Effect.gen(function* () {
const mcp = yield* MCP.Service
const status = Effect.fn("McpHttpApi.status")(function* () {
return yield* mcp.status()
})
const add = Effect.fn("McpHttpApi.add")(function* (ctx: { payload: typeof AddPayload.Type }) {
const payload = Schema.decodeUnknownSync(AddPayload)(ctx.payload)
const result = (yield* mcp.add(payload.name, payload.config)).status
return Schema.decodeUnknownSync(StatusMap)("status" in result ? { [payload.name]: result } : result)
})
const connect = Effect.fn("McpHttpApi.connect")(function* (ctx: { params: { name: string } }) {
yield* mcp.connect(ctx.params.name)
return true
})
const disconnect = Effect.fn("McpHttpApi.disconnect")(function* (ctx: { params: { name: string } }) {
yield* mcp.disconnect(ctx.params.name)
return true
})
return HttpApiBuilder.group(McpApi, "mcp", (handlers) =>
handlers.handle("status", status).handle("add", add).handle("connect", connect).handle("disconnect", disconnect),
)
}),
).pipe(Layer.provide(MCP.defaultLayer))