summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-24 21:04:49 +0900
committerAdam Malczewski <[email protected]>2026-06-24 21:04:49 +0900
commit1b2a13e29e98da04d55c061c2dcadb8c36d783cd (patch)
treeca818510b2337f2cd291aff61e0171712a1435f5
parent5c37d89f412f0dbb6fa798b16e1d4bb91dc58595 (diff)
downloaddispatch-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.md117
-rw-r--r--packages/transport-contract/package.json2
-rw-r--r--packages/transport-contract/src/contract.types.test.ts22
-rw-r--r--packages/transport-contract/src/index.ts31
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