diff options
| author | Adam Malczewski <[email protected]> | 2026-06-24 21:04:49 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-24 21:04:49 +0900 |
| commit | 1b2a13e29e98da04d55c061c2dcadb8c36d783cd (patch) | |
| tree | ca818510b2337f2cd291aff61e0171712a1435f5 | |
| parent | 5c37d89f412f0dbb6fa798b16e1d4bb91dc58595 (diff) | |
| download | dispatch-1b2a13e29e98da04d55c061c2dcadb8c36d783cd.tar.gz dispatch-1b2a13e29e98da04d55c061c2dcadb8c36d783cd.zip | |
feat(transport-contract): add McpServerInfo + McpStatusResponse (0.22.0)
Additive types for GET /conversations/:id/mcp status endpoint, mirroring the
existing LSP status types. McpServerState, McpServerInfo, McpStatusResponse.
+2 type-test assertions. Version bump 0.21.0 → 0.22.0.
Handoff written: frontend-mcp-status-handoff.md (backend route + FE consumption).
| -rw-r--r-- | frontend-mcp-status-handoff.md | 117 | ||||
| -rw-r--r-- | packages/transport-contract/package.json | 2 | ||||
| -rw-r--r-- | packages/transport-contract/src/contract.types.test.ts | 22 | ||||
| -rw-r--r-- | packages/transport-contract/src/index.ts | 31 |
4 files changed, 171 insertions, 1 deletions
diff --git a/frontend-mcp-status-handoff.md b/frontend-mcp-status-handoff.md new file mode 100644 index 0000000..d19a9f1 --- /dev/null +++ b/frontend-mcp-status-handoff.md @@ -0,0 +1,117 @@ +# Handoff — MCP Status Endpoint (backend + frontend) + +## Backend: `GET /conversations/:id/mcp` (transport-http) + +Mirror the existing `GET /conversations/:id/lsp` endpoint exactly. The contract +types are already in `@dispatch/transport-contract` 0.22.0: + +```typescript +export type McpServerState = "connecting" | "connected" | "error" | "disconnected"; + +export interface McpServerInfo { + readonly id: string; + readonly state: McpServerState; + readonly error?: string; + readonly toolCount: number; + readonly configSource?: string; +} + +export interface McpStatusResponse { + readonly conversationId: string; + readonly cwd: string | null; + readonly servers: readonly McpServerInfo[]; +} +``` + +### What to change in `packages/transport-http/` + +1. **`src/seam.ts`** — add re-exports from `@dispatch/mcp`: + ```typescript + export type { McpServerStatus, McpService } from "@dispatch/mcp"; + export { mcpServiceHandle } from "@dispatch/mcp"; + ``` + +2. **`src/app.ts`** — add `mcpService?` to `CreateServerOptions` (optional, same + as `lspService?`), then add the route: + ```typescript + app.get("/conversations/:id/mcp", async (c) => { + // Mirror the LSP route exactly: + // 1. Gate on persisted cwd (getCwd) — return {cwd:null, servers:[]} when null + // 2. Resolve effective cwd (getEffectiveCwd) — return {cwd:null, servers:[]} when null + // 3. If opts.mcpService === undefined → 503 { error: "MCP service not available" } + // 4. Call opts.mcpService.status(effectiveCwd) → McpServerStatus[] + // 5. Map McpServerStatus → McpServerInfo (id, state, error?, toolCount, configSource?) + // 6. Return McpStatusResponse { conversationId, cwd: effectiveCwd, servers } + }); + ``` + +3. **`src/extension.ts`** — add `host.getService(mcpServiceHandle)` alongside + `lspService`, and pass `mcpService` to `createApp({...})`. + +4. **`package.json`** — add `"@dispatch/mcp": "workspace:*"` to dependencies. + +5. **Tests** — mirror the LSP status tests: + - Returns null+empty when no persisted cwd — `mcpService.status` NOT called. + - Returns servers when cwd is set. + - Returns 503 when `mcpService` is undefined. + - Maps `McpServerStatus` → `McpServerInfo` correctly (error omitted when + undefined, configSource omitted when undefined — honor `exactOptionalPropertyTypes`). + +### McpServerStatus → McpServerInfo mapping + +The `McpService.status(cwd)` returns `McpServerStatus[]` from `@dispatch/mcp`: +```typescript +interface McpServerStatus { + readonly id: string; + readonly state: "connecting" | "connected" | "error" | "disconnected"; + readonly error?: string; + readonly toolCount: number; +} +``` +Map to `McpServerInfo` (same fields, conditionally include `error` per +`exactOptionalPropertyTypes`). Note: `McpServerStatus` does NOT have +`configSource` — that field is on `ResolvedMcpServer` (from config resolution). +If you want to include `configSource` in the status response, the `McpService` +interface or `McpServerStatus` would need to be extended. For Phase 2, omit +`configSource` (it's optional on `McpServerInfo`) unless the MCP extension is +updated to include it in the status. + +--- + +## Frontend (dispatch-web): consume `GET /conversations/:id/mcp` + +### What to do + +1. **Re-pin** `@dispatch/transport-contract` to `0.22.0`. +2. **Re-mirror** the reference snapshot if one exists. +3. **Add a fetch** for `GET /conversations/:id/mcp` — mirror how `GET /conversations/:id/lsp` + is fetched and displayed. +4. **Render** the MCP server status: each server's `id`, `state` (with the same + connected/error/starting visual treatment as LSP), `toolCount`, and optional + `error`. +5. Place the MCP status UI alongside (or below) the LSP status in the conversation + settings/panel — they're sibling features. + +### Response shape + +```json +{ + "conversationId": "abc-123", + "cwd": "/home/user/project", + "servers": [ + { + "id": "freecad", + "state": "connected", + "toolCount": 12 + }, + { + "id": "chrome-devtools", + "state": "error", + "error": "Executable not found in $PATH: npx", + "toolCount": 0 + } + ] +} +``` + +When no cwd is set: `{ "conversationId": "abc-123", "cwd": null, "servers": [] }`. diff --git a/packages/transport-contract/package.json b/packages/transport-contract/package.json index 34c7fc4..842a28b 100644 --- a/packages/transport-contract/package.json +++ b/packages/transport-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dispatch/transport-contract", - "version": "0.21.0", + "version": "0.22.0", "type": "module", "private": true, "main": "dist/index.js", diff --git a/packages/transport-contract/src/contract.types.test.ts b/packages/transport-contract/src/contract.types.test.ts index e022812..6d0129c 100644 --- a/packages/transport-contract/src/contract.types.test.ts +++ b/packages/transport-contract/src/contract.types.test.ts @@ -12,6 +12,7 @@ import type { LspServerInfo, LspServerState, LspStatusResponse, + McpStatusResponse, SetCwdRequest, } from "./index.js"; @@ -129,4 +130,25 @@ describe("transport-contract types compile and are exported", () => { it("LspStatusResponse: populated servers when cwd is set", () => { expect(_lspWithServers.servers).toHaveLength(2); }); + + // ─── MCP status ───────────────────────────────────────────────────────────── + + it("McpStatusResponse: empty servers when cwd is null", () => { + const _noCwd: McpStatusResponse = { conversationId: "c1", cwd: null, servers: [] }; + expect(_noCwd.servers).toEqual([]); + }); + + it("McpStatusResponse: populated servers when cwd is set", () => { + const _withServers: McpStatusResponse = { + conversationId: "c2", + cwd: "/home/user/project", + servers: [ + { id: "freecad", state: "connected", toolCount: 12, configSource: ".dispatch/mcp.json" }, + { id: "chrome", state: "error", error: "spawn failed", toolCount: 0 }, + ], + }; + expect(_withServers.servers).toHaveLength(2); + expect(_withServers.servers[0]?.toolCount).toBe(12); + expect(_withServers.servers[1]?.error).toBe("spawn failed"); + }); }); diff --git a/packages/transport-contract/src/index.ts b/packages/transport-contract/src/index.ts index c251ae4..4e3e7dc 100644 --- a/packages/transport-contract/src/index.ts +++ b/packages/transport-contract/src/index.ts @@ -455,6 +455,37 @@ export interface LspStatusResponse { readonly servers: readonly LspServerInfo[]; } +// ─── MCP status ────────────────────────────────────────────────────── + +export type McpServerState = "connecting" | "connected" | "error" | "disconnected"; + +/** One MCP server's status as reported to the frontend. */ +export interface McpServerInfo { + /** Stable server id (the config key from `.dispatch/mcp.json`), e.g. "freecad". */ + readonly id: string; + /** Current connection state. */ + readonly state: McpServerState; + /** Present only when `state === "error"`: a short human-readable reason. */ + readonly error?: string; + /** Number of tools discovered from this server. */ + readonly toolCount: number; + /** Which config source this server was resolved from. */ + readonly configSource?: string; +} + +/** Response of `GET /conversations/:id/mcp`. */ +export interface McpStatusResponse { + readonly conversationId: string; + /** + * The resolved working directory the MCP servers are configured for, or + * `null` when no cwd has been set for the conversation (then `servers` is + * empty). Mirrors the LSP status endpoint behavior. + */ + readonly cwd: string | null; + /** The MCP servers configured for `cwd` and their live state. */ + readonly servers: readonly McpServerInfo[]; +} + /** * Request body for `POST /chat/warm` — manually trigger a prompt-cache WARMING * request for a conversation (e.g. a frontend "warm now" button, or fast tests |
