summaryrefslogtreecommitdiffhomepage
path: root/src/features/mcp/logic
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-26 22:21:55 +0900
committerAdam Malczewski <[email protected]>2026-06-26 22:23:39 +0900
commitc333fcec32b1f90bf0da6bb14d2609c20e38a74f (patch)
tree0db3ec77a6838c4f800c362df0de3c6cd8544431 /src/features/mcp/logic
parent1285564f12238b22f6b39b9f3fbcecaca8456911 (diff)
downloaddispatch-web-c333fcec32b1f90bf0da6bb14d2609c20e38a74f.tar.gz
dispatch-web-c333fcec32b1f90bf0da6bb14d2609c20e38a74f.zip
style: switch from tabs to 2-space indentation (incl. svelte)
Diffstat (limited to 'src/features/mcp/logic')
-rw-r--r--src/features/mcp/logic/view-model.test.ts136
-rw-r--r--src/features/mcp/logic/view-model.ts128
2 files changed, 132 insertions, 132 deletions
diff --git a/src/features/mcp/logic/view-model.test.ts b/src/features/mcp/logic/view-model.test.ts
index 23bf20f..d1a77d1 100644
--- a/src/features/mcp/logic/view-model.test.ts
+++ b/src/features/mcp/logic/view-model.test.ts
@@ -3,86 +3,86 @@ import { describe, expect, it } from "vitest";
import { summarizeMcpServers, viewMcpServer, viewMcpServers } from "./view-model";
const server = (over: Partial<McpServerInfo> = {}): McpServerInfo => ({
- id: "freecad",
- state: "connected",
- toolCount: 12,
- ...over,
+ id: "freecad",
+ state: "connected",
+ toolCount: 12,
+ ...over,
});
describe("viewMcpServer", () => {
- it("connected → success badge, not busy, no error, passes toolCount", () => {
- const v = viewMcpServer(server({ toolCount: 5 }));
- expect(v.badge).toBe("success");
- expect(v.statusLabel).toBe("Connected");
- expect(v.busy).toBe(false);
- expect(v.error).toBeNull();
- expect(v.toolCount).toBe(5);
- expect(v.configSource).toBeNull();
- });
+ it("connected → success badge, not busy, no error, passes toolCount", () => {
+ const v = viewMcpServer(server({ toolCount: 5 }));
+ expect(v.badge).toBe("success");
+ expect(v.statusLabel).toBe("Connected");
+ expect(v.busy).toBe(false);
+ expect(v.error).toBeNull();
+ expect(v.toolCount).toBe(5);
+ expect(v.configSource).toBeNull();
+ });
- it("connecting → warning badge + busy (spinner)", () => {
- const v = viewMcpServer(server({ state: "connecting" }));
- expect(v.badge).toBe("warning");
- expect(v.statusLabel).toBe("Connecting…");
- expect(v.busy).toBe(true);
- expect(v.error).toBeNull();
- });
+ it("connecting → warning badge + busy (spinner)", () => {
+ const v = viewMcpServer(server({ state: "connecting" }));
+ expect(v.badge).toBe("warning");
+ expect(v.statusLabel).toBe("Connecting…");
+ expect(v.busy).toBe(true);
+ expect(v.error).toBeNull();
+ });
- it("disconnected → neutral badge, not busy", () => {
- const v = viewMcpServer(server({ state: "disconnected" }));
- expect(v.badge).toBe("neutral");
- expect(v.statusLabel).toBe("Disconnected");
- expect(v.busy).toBe(false);
- expect(v.error).toBeNull();
- });
+ it("disconnected → neutral badge, not busy", () => {
+ const v = viewMcpServer(server({ state: "disconnected" }));
+ expect(v.badge).toBe("neutral");
+ expect(v.statusLabel).toBe("Disconnected");
+ expect(v.busy).toBe(false);
+ expect(v.error).toBeNull();
+ });
- it("error → error badge + surfaces the reason (with a fallback)", () => {
- const withReason = viewMcpServer(server({ state: "error", error: "ENOENT: npx" }));
- expect(withReason.badge).toBe("error");
- expect(withReason.busy).toBe(false);
- expect(withReason.error).toBe("ENOENT: npx");
+ it("error → error badge + surfaces the reason (with a fallback)", () => {
+ const withReason = viewMcpServer(server({ state: "error", error: "ENOENT: npx" }));
+ expect(withReason.badge).toBe("error");
+ expect(withReason.busy).toBe(false);
+ expect(withReason.error).toBe("ENOENT: npx");
- const noReason = viewMcpServer(server({ state: "error" }));
- expect(noReason.error).toBe("Failed to connect");
- });
+ const noReason = viewMcpServer(server({ state: "error" }));
+ expect(noReason.error).toBe("Failed to connect");
+ });
- it("passes through configSource when present", () => {
- const v = viewMcpServer(server({ configSource: ".dispatch/mcp.json" }));
- expect(v.configSource).toBe(".dispatch/mcp.json");
- });
+ it("passes through configSource when present", () => {
+ const v = viewMcpServer(server({ configSource: ".dispatch/mcp.json" }));
+ expect(v.configSource).toBe(".dispatch/mcp.json");
+ });
- it("viewMcpServers maps a list preserving order", () => {
- const views = viewMcpServers([server({ id: "a" }), server({ id: "b" })]);
- expect(views.map((v) => v.id)).toEqual(["a", "b"]);
- });
+ it("viewMcpServers maps a list preserving order", () => {
+ const views = viewMcpServers([server({ id: "a" }), server({ id: "b" })]);
+ expect(views.map((v) => v.id)).toEqual(["a", "b"]);
+ });
});
describe("summarizeMcpServers", () => {
- it("empty list", () => {
- expect(summarizeMcpServers([])).toBe("No MCP servers");
- });
+ it("empty list", () => {
+ expect(summarizeMcpServers([])).toBe("No MCP servers");
+ });
- it("counts connected / connecting / disconnected / errors", () => {
- expect(summarizeMcpServers([server({ state: "connected" })])).toBe("1 connected");
- expect(
- summarizeMcpServers([
- server({ id: "a", state: "connected" }),
- server({ id: "b", state: "error" }),
- ]),
- ).toBe("1 connected, 1 error");
- expect(
- summarizeMcpServers([
- server({ id: "a", state: "connected" }),
- server({ id: "b", state: "connecting" }),
- server({ id: "c", state: "disconnected" }),
- server({ id: "d", state: "error" }),
- server({ id: "e", state: "error" }),
- ]),
- ).toBe("1 connected, 1 connecting, 1 disconnected, 2 errors");
- });
+ it("counts connected / connecting / disconnected / errors", () => {
+ expect(summarizeMcpServers([server({ state: "connected" })])).toBe("1 connected");
+ expect(
+ summarizeMcpServers([
+ server({ id: "a", state: "connected" }),
+ server({ id: "b", state: "error" }),
+ ]),
+ ).toBe("1 connected, 1 error");
+ expect(
+ summarizeMcpServers([
+ server({ id: "a", state: "connected" }),
+ server({ id: "b", state: "connecting" }),
+ server({ id: "c", state: "disconnected" }),
+ server({ id: "d", state: "error" }),
+ server({ id: "e", state: "error" }),
+ ]),
+ ).toBe("1 connected, 1 connecting, 1 disconnected, 2 errors");
+ });
- it("lists only non-zero buckets", () => {
- expect(summarizeMcpServers([server({ state: "disconnected" })])).toBe("1 disconnected");
- expect(summarizeMcpServers([server({ id: "a", state: "connecting" })])).toBe("1 connecting");
- });
+ it("lists only non-zero buckets", () => {
+ expect(summarizeMcpServers([server({ state: "disconnected" })])).toBe("1 disconnected");
+ expect(summarizeMcpServers([server({ id: "a", state: "connecting" })])).toBe("1 connecting");
+ });
});
diff --git a/src/features/mcp/logic/view-model.ts b/src/features/mcp/logic/view-model.ts
index 247f804..fdff78a 100644
--- a/src/features/mcp/logic/view-model.ts
+++ b/src/features/mcp/logic/view-model.ts
@@ -16,8 +16,8 @@ import type { McpServerInfo, McpServerState } from "@dispatch/transport-contract
/** Outcome of `GET /conversations/:id/mcp`; `null` when no real conversation is focused. */
export type McpStatusResult =
- | { readonly ok: true; readonly cwd: string | null; readonly servers: readonly McpServerInfo[] }
- | { readonly ok: false; readonly error: string };
+ | { readonly ok: true; readonly cwd: string | null; readonly servers: readonly McpServerInfo[] }
+ | { readonly ok: false; readonly error: string };
export type LoadMcpStatus = () => Promise<McpStatusResult | null>;
@@ -26,18 +26,18 @@ export type LoadMcpStatus = () => Promise<McpStatusResult | null>;
export type Badge = "success" | "warning" | "error" | "neutral";
export interface McpServerView {
- readonly id: string;
- readonly state: McpServerState;
- readonly statusLabel: string;
- readonly badge: Badge;
- /** True while the state is transient (show a spinner). */
- readonly busy: boolean;
- /** The error reason when `state === "error"`, else null. */
- readonly error: string | null;
- /** Number of tools discovered from this server. */
- readonly toolCount: number;
- /** Which config source the server was resolved from, else null. */
- readonly configSource: string | null;
+ readonly id: string;
+ readonly state: McpServerState;
+ readonly statusLabel: string;
+ readonly badge: Badge;
+ /** True while the state is transient (show a spinner). */
+ readonly busy: boolean;
+ /** The error reason when `state === "error"`, else null. */
+ readonly error: string | null;
+ /** Number of tools discovered from this server. */
+ readonly toolCount: number;
+ /** Which config source the server was resolved from, else null. */
+ readonly configSource: string | null;
}
/**
@@ -47,42 +47,42 @@ export interface McpServerView {
* → error, and `disconnected` (a stable idle state) → neutral.
*/
export function viewMcpServer(server: McpServerInfo): McpServerView {
- let statusLabel: string;
- let badge: Badge;
- let busy = false;
- switch (server.state) {
- case "connected":
- statusLabel = "Connected";
- badge = "success";
- break;
- case "connecting":
- statusLabel = "Connecting…";
- badge = "warning";
- busy = true;
- break;
- case "disconnected":
- statusLabel = "Disconnected";
- badge = "neutral";
- break;
- case "error":
- statusLabel = "Error";
- badge = "error";
- break;
- }
- return {
- id: server.id,
- state: server.state,
- statusLabel,
- badge,
- busy,
- error: server.state === "error" ? (server.error ?? "Failed to connect") : null,
- toolCount: server.toolCount,
- configSource: server.configSource ?? null,
- };
+ let statusLabel: string;
+ let badge: Badge;
+ let busy = false;
+ switch (server.state) {
+ case "connected":
+ statusLabel = "Connected";
+ badge = "success";
+ break;
+ case "connecting":
+ statusLabel = "Connecting…";
+ badge = "warning";
+ busy = true;
+ break;
+ case "disconnected":
+ statusLabel = "Disconnected";
+ badge = "neutral";
+ break;
+ case "error":
+ statusLabel = "Error";
+ badge = "error";
+ break;
+ }
+ return {
+ id: server.id,
+ state: server.state,
+ statusLabel,
+ badge,
+ busy,
+ error: server.state === "error" ? (server.error ?? "Failed to connect") : null,
+ toolCount: server.toolCount,
+ configSource: server.configSource ?? null,
+ };
}
export function viewMcpServers(servers: readonly McpServerInfo[]): readonly McpServerView[] {
- return servers.map(viewMcpServer);
+ return servers.map(viewMcpServer);
}
/**
@@ -90,21 +90,21 @@ export function viewMcpServers(servers: readonly McpServerInfo[]): readonly McpS
* 1 error". Only non-zero buckets are listed.
*/
export function summarizeMcpServers(servers: readonly McpServerInfo[]): string {
- if (servers.length === 0) return "No MCP servers";
- let connected = 0;
- let connecting = 0;
- let disconnected = 0;
- let errored = 0;
- for (const s of servers) {
- if (s.state === "connected") connected++;
- else if (s.state === "error") errored++;
- else if (s.state === "connecting") connecting++;
- else disconnected++;
- }
- const parts: string[] = [];
- if (connected > 0) parts.push(`${connected} connected`);
- if (connecting > 0) parts.push(`${connecting} connecting`);
- if (disconnected > 0) parts.push(`${disconnected} disconnected`);
- if (errored > 0) parts.push(`${errored} error${errored === 1 ? "" : "s"}`);
- return parts.join(", ");
+ if (servers.length === 0) return "No MCP servers";
+ let connected = 0;
+ let connecting = 0;
+ let disconnected = 0;
+ let errored = 0;
+ for (const s of servers) {
+ if (s.state === "connected") connected++;
+ else if (s.state === "error") errored++;
+ else if (s.state === "connecting") connecting++;
+ else disconnected++;
+ }
+ const parts: string[] = [];
+ if (connected > 0) parts.push(`${connected} connected`);
+ if (connecting > 0) parts.push(`${connecting} connecting`);
+ if (disconnected > 0) parts.push(`${disconnected} disconnected`);
+ if (errored > 0) parts.push(`${errored} error${errored === 1 ? "" : "s"}`);
+ return parts.join(", ");
}