mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-20 09:14:22 +01:00
core: improve MCP reliability and add status monitoring
- Added 5-second timeout to MCP client verification to prevent hanging connections - New GET /mcp endpoint to monitor server connection status - Automatically removes unresponsive MCP clients during initialization
This commit is contained in:
@@ -9,6 +9,7 @@ import z from "zod/v4"
|
||||
import { Session } from "../session"
|
||||
import { Bus } from "../bus"
|
||||
import { Instance } from "../project/instance"
|
||||
import { withTimeout } from "@/util/timeout"
|
||||
|
||||
export namespace MCP {
|
||||
const log = Log.create({ service: "mcp" })
|
||||
@@ -20,11 +21,13 @@ export namespace MCP {
|
||||
}),
|
||||
)
|
||||
|
||||
type MCPClient = Awaited<ReturnType<typeof experimental_createMCPClient>>
|
||||
|
||||
const state = Instance.state(
|
||||
async () => {
|
||||
const cfg = await Config.get()
|
||||
const clients: {
|
||||
[name: string]: Awaited<ReturnType<typeof experimental_createMCPClient>>
|
||||
[name: string]: MCPClient
|
||||
} = {}
|
||||
for (const [key, mcp] of Object.entries(cfg.mcp ?? {})) {
|
||||
if (mcp.enabled === false) {
|
||||
@@ -128,8 +131,17 @@ export namespace MCP {
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, client] of Object.entries(clients)) {
|
||||
const result = await withTimeout(client.tools(), 5000).catch(() => {})
|
||||
if (!result) {
|
||||
log.warn("mcp client verification failed, removing client", { key })
|
||||
delete clients[key]
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
clients,
|
||||
config: cfg.mcp ?? {},
|
||||
}
|
||||
},
|
||||
async (state) => {
|
||||
@@ -139,6 +151,23 @@ export namespace MCP {
|
||||
},
|
||||
)
|
||||
|
||||
export async function status() {
|
||||
return state().then((state) => {
|
||||
const result: Record<string, "connected" | "failed" | "disabled"> = {}
|
||||
for (const [key, client] of Object.entries(state.config)) {
|
||||
if (client.enabled === false) {
|
||||
result[key] = "disabled"
|
||||
continue
|
||||
}
|
||||
if (state.clients[key]) {
|
||||
result[key] = "connected"
|
||||
}
|
||||
result[key] = "failed"
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
export async function clients() {
|
||||
return state().then((state) => state.clients)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import { SessionRevert } from "../session/revert"
|
||||
import { lazy } from "../util/lazy"
|
||||
import { Todo } from "../session/todo"
|
||||
import { InstanceBootstrap } from "../project/bootstrap"
|
||||
import { MCP } from "../mcp"
|
||||
|
||||
const ERRORS = {
|
||||
400: {
|
||||
@@ -1183,6 +1184,26 @@ export namespace Server {
|
||||
return c.json(modes)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/mcp",
|
||||
describeRoute({
|
||||
description: "Get MCP server status",
|
||||
operationId: "mcp.status",
|
||||
responses: {
|
||||
200: {
|
||||
description: "MCP server status",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.any()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
return c.json(await MCP.status())
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/tui/append-prompt",
|
||||
describeRoute({
|
||||
|
||||
@@ -82,6 +82,8 @@ import type {
|
||||
AppLogResponses,
|
||||
AppAgentsData,
|
||||
AppAgentsResponses,
|
||||
McpStatusData,
|
||||
McpStatusResponses,
|
||||
TuiAppendPromptData,
|
||||
TuiAppendPromptResponses,
|
||||
TuiOpenHelpData,
|
||||
@@ -567,6 +569,18 @@ class App extends _HeyApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
class Mcp extends _HeyApiClient {
|
||||
/**
|
||||
* Get MCP server status
|
||||
*/
|
||||
public status<ThrowOnError extends boolean = false>(options?: Options<McpStatusData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<McpStatusResponses, unknown, ThrowOnError>({
|
||||
url: "/mcp",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Tui extends _HeyApiClient {
|
||||
/**
|
||||
* Append prompt to the TUI
|
||||
@@ -724,6 +738,7 @@ export class OpencodeClient extends _HeyApiClient {
|
||||
find = new Find({ client: this._client })
|
||||
file = new File({ client: this._client })
|
||||
app = new App({ client: this._client })
|
||||
mcp = new Mcp({ client: this._client })
|
||||
tui = new Tui({ client: this._client })
|
||||
auth = new Auth({ client: this._client })
|
||||
event = new Event({ client: this._client })
|
||||
|
||||
@@ -2070,6 +2070,22 @@ export type AppAgentsResponses = {
|
||||
|
||||
export type AppAgentsResponse = AppAgentsResponses[keyof AppAgentsResponses]
|
||||
|
||||
export type McpStatusData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
directory?: string
|
||||
}
|
||||
url: "/mcp"
|
||||
}
|
||||
|
||||
export type McpStatusResponses = {
|
||||
/**
|
||||
* MCP server status
|
||||
*/
|
||||
200: unknown
|
||||
}
|
||||
|
||||
export type TuiAppendPromptData = {
|
||||
body?: {
|
||||
text: string
|
||||
|
||||
Reference in New Issue
Block a user