import { describe, expect, it } from "@effect/vitest";import { Effect } from "effect"; import { createExecutor, makeTestConfig } from "@executor/sdk"; import { mcpPlugin } from "./plugin";import { extractManifestFromListToolsResult, deriveMcpNamespace, joinToolPath,} from "./manifest"; // ---------------------------------------------------------------------------// Manifest extraction// --------------------------------------------------------------------------- describe("extractManifestFromListToolsResult", () => { it.effect("extracts tools from a valid listTools response", () => Effect.sync(() => { const result = extractManifestFromListToolsResult({ tools: [ { name: "get_weather", description: "Get weather for a location", inputSchema: { type: "object", properties: { location: { type: "string" } }, }, }, { name: "search", description: "Search the web" }, ], }); expect(result.tools).toHaveLength(2); expect(result.tools[0]!.toolName).toBe("get_weather"); expect(result.tools[0]!.toolId).toBe("get_weather"); expect(result.tools[0]!.description).toBe("Get weather for a location"); expect(result.tools[1]!.toolName).toBe("search"); }), ); it.effect("sanitizes tool IDs", () => Effect.sync(() => { const result = extractManifestFromListToolsResult({ tools: [ { name: "My Tool!!", description: null }, { name: "My Tool!!", description: null }, ], }); expect(result.tools[0]!.toolId).toBe("my_tool"); expect(result.tools[1]!.toolId).toBe("my_tool_2"); }), ); it.effect("handles empty tools list", () => Effect.sync(() => { const result = extractManifestFromListToolsResult({ tools: [] }); expect(result.tools).toHaveLength(0); }), ); it.effect("extracts server metadata", () => Effect.sync(() => { const result = extractManifestFromListToolsResult( { tools: [] }, { serverInfo: { name: "test-server", version: "1.0.0" } }, ); expect(result.server?.name).toBe("test-server"); expect(result.server?.version).toBe("1.0.0"); }), );}); // ---------------------------------------------------------------------------// Namespace derivation// --------------------------------------------------------------------------- describe("deriveMcpNamespace", () => { it.effect("derives from name", () => Effect.sync(() => { expect(deriveMcpNamespace({ name: "GitHub MCP" })).toBe("github_mcp"); }), ); it.effect("derives from endpoint", () => Effect.sync(() => { expect(deriveMcpNamespace({ endpoint: "https://api.example.com/mcp" })).toBe( "api_example_com", ); }), ); it.effect("derives from command", () => Effect.sync(() => { expect(deriveMcpNamespace({ command: "/usr/local/bin/my-mcp-server" })).toBe( "my_mcp_server", ); }), ); it.effect("falls back to 'mcp'", () => Effect.sync(() => { expect(deriveMcpNamespace({})).toBe("mcp"); }), );}); // ---------------------------------------------------------------------------// joinToolPath// --------------------------------------------------------------------------- describe("joinToolPath", () => { it.effect("joins namespace and toolId", () => Effect.sync(() => { expect(joinToolPath("github", "search")).toBe("github.search"); }), ); it.effect("returns toolId when namespace is undefined", () => Effect.sync(() => { expect(joinToolPath(undefined, "search")).toBe("search"); }), );}); // ---------------------------------------------------------------------------// Plugin lifecycle// --------------------------------------------------------------------------- describe("mcpPlugin", () => { it.effect("creates executor with mcp plugin", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [mcpPlugin()] as const, }), ); expect(executor.mcp).toBeDefined(); expect(executor.mcp.addSource).toBeTypeOf("function"); expect(executor.mcp.removeSource).toBeTypeOf("function"); expect(executor.mcp.refreshSource).toBeTypeOf("function"); expect(executor.mcp.probeEndpoint).toBeTypeOf("function"); expect(executor.mcp.startOAuth).toBeTypeOf("function"); expect(executor.mcp.completeOAuth).toBeTypeOf("function"); }), ); it.effect("sources list is initially empty", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [mcpPlugin()] as const }), ); const sources = yield* executor.sources.list(); expect(sources).toHaveLength(0); }), ); it.effect("tools list is initially empty", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [mcpPlugin()] as const }), ); const tools = yield* executor.tools.list(); expect(tools).toHaveLength(0); }), ); // When discovery fails (auth, network, etc.) we still want the source // row to land in the DB so users see it in the catalog — they can // retry via refresh once they fix the underlying problem. The error // still propagates to the caller so boot-time sync logs the reason. it.effect("registers source with 0 tools when discovery fails", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [mcpPlugin()] as const }), ); const result = yield* executor.mcp .addSource({ transport: "remote", name: "broken", // Port 1 is reserved — will connection-refused immediately, // giving us a deterministic discovery failure without any // server mocks. endpoint: "http://127.0.0.1:1/mcp", remoteTransport: "auto", namespace: "broken_source", }) .pipe(Effect.either); expect(result._tag).toBe("Left"); const sources = yield* executor.sources.list(); const broken = sources.find((s) => s.id === "broken_source"); expect(broken).toBeDefined(); expect(broken?.kind).toBe("mcp"); expect(broken?.pluginId).toBe("mcp"); const tools = yield* executor.tools.list(); expect(tools.filter((t) => t.sourceId === "broken_source")).toHaveLength(0); }), );});
import { describe, expect, it } from "@effect/vitest";
import { Effect } from "effect";
import { createExecutor, makeTestConfig } from "@executor/sdk";
import { mcpPlugin } from "./plugin";
import {
extractManifestFromListToolsResult,
deriveMcpNamespace,
joinToolPath,
} from "./manifest";
// ---------------------------------------------------------------------------
// Manifest extraction
// ---------------------------------------------------------------------------
describe("extractManifestFromListToolsResult", () => {
it.effect("extracts tools from a valid listTools response", () =>
Effect.sync(() => {
const result = extractManifestFromListToolsResult({
tools: [
{
name: "get_weather",
description: "Get weather for a location",
inputSchema: {
type: "object",
properties: { location: { type: "string" } },
},
},
{ name: "search", description: "Search the web" },
],
});
expect(result.tools).toHaveLength(2);
expect(result.tools[0]!.toolName).toBe("get_weather");
expect(result.tools[0]!.toolId).toBe("get_weather");
expect(result.tools[0]!.description).toBe("Get weather for a location");
expect(result.tools[1]!.toolName).toBe("search");
}),
);
it.effect("sanitizes tool IDs", () =>
Effect.sync(() => {
const result = extractManifestFromListToolsResult({
tools: [
{ name: "My Tool!!", description: null },
{ name: "My Tool!!", description: null },
],
});
expect(result.tools[0]!.toolId).toBe("my_tool");
expect(result.tools[1]!.toolId).toBe("my_tool_2");
}),
);
it.effect("handles empty tools list", () =>
Effect.sync(() => {
const result = extractManifestFromListToolsResult({ tools: [] });
expect(result.tools).toHaveLength(0);
}),
);
it.effect("extracts server metadata", () =>
Effect.sync(() => {
const result = extractManifestFromListToolsResult(
{ tools: [] },
{ serverInfo: { name: "test-server", version: "1.0.0" } },
);
expect(result.server?.name).toBe("test-server");
expect(result.server?.version).toBe("1.0.0");
}),
);
});
// ---------------------------------------------------------------------------
// Namespace derivation
// ---------------------------------------------------------------------------
describe("deriveMcpNamespace", () => {
it.effect("derives from name", () =>
Effect.sync(() => {
expect(deriveMcpNamespace({ name: "GitHub MCP" })).toBe("github_mcp");
}),
);
it.effect("derives from endpoint", () =>
Effect.sync(() => {
expect(deriveMcpNamespace({ endpoint: "https://api.example.com/mcp" })).toBe(
"api_example_com",
);
}),
);
it.effect("derives from command", () =>
Effect.sync(() => {
expect(deriveMcpNamespace({ command: "/usr/local/bin/my-mcp-server" })).toBe(
"my_mcp_server",
);
}),
);
it.effect("falls back to 'mcp'", () =>
Effect.sync(() => {
expect(deriveMcpNamespace({})).toBe("mcp");
}),
);
});
// ---------------------------------------------------------------------------
// joinToolPath
// ---------------------------------------------------------------------------
describe("joinToolPath", () => {
it.effect("joins namespace and toolId", () =>
Effect.sync(() => {
expect(joinToolPath("github", "search")).toBe("github.search");
}),
);
it.effect("returns toolId when namespace is undefined", () =>
Effect.sync(() => {
expect(joinToolPath(undefined, "search")).toBe("search");
}),
);
});
// ---------------------------------------------------------------------------
// Plugin lifecycle
// ---------------------------------------------------------------------------
describe("mcpPlugin", () => {
it.effect("creates executor with mcp plugin", () =>
Effect.gen(function* () {
const executor = yield* createExecutor(
makeTestConfig({
plugins: [mcpPlugin()] as const,
}),
);
expect(executor.mcp).toBeDefined();
expect(executor.mcp.addSource).toBeTypeOf("function");
expect(executor.mcp.removeSource).toBeTypeOf("function");
expect(executor.mcp.refreshSource).toBeTypeOf("function");
expect(executor.mcp.probeEndpoint).toBeTypeOf("function");
expect(executor.mcp.startOAuth).toBeTypeOf("function");
expect(executor.mcp.completeOAuth).toBeTypeOf("function");
}),
);
it.effect("sources list is initially empty", () =>
Effect.gen(function* () {
const executor = yield* createExecutor(
makeTestConfig({ plugins: [mcpPlugin()] as const }),
);
const sources = yield* executor.sources.list();
expect(sources).toHaveLength(0);
}),
);
it.effect("tools list is initially empty", () =>
Effect.gen(function* () {
const executor = yield* createExecutor(
makeTestConfig({ plugins: [mcpPlugin()] as const }),
);
const tools = yield* executor.tools.list();
expect(tools).toHaveLength(0);
}),
);
// When discovery fails (auth, network, etc.) we still want the source
// row to land in the DB so users see it in the catalog — they can
// retry via refresh once they fix the underlying problem. The error
// still propagates to the caller so boot-time sync logs the reason.
it.effect("registers source with 0 tools when discovery fails", () =>
Effect.gen(function* () {
const executor = yield* createExecutor(
makeTestConfig({ plugins: [mcpPlugin()] as const }),
);
const result = yield* executor.mcp
.addSource({
transport: "remote",
name: "broken",
// Port 1 is reserved — will connection-refused immediately,
// giving us a deterministic discovery failure without any
// server mocks.
endpoint: "http://127.0.0.1:1/mcp",
remoteTransport: "auto",
namespace: "broken_source",
})
.pipe(Effect.either);
expect(result._tag).toBe("Left");
const sources = yield* executor.sources.list();
const broken = sources.find((s) => s.id === "broken_source");
expect(broken).toBeDefined();
expect(broken?.kind).toBe("mcp");
expect(broken?.pluginId).toBe("mcp");
const tools = yield* executor.tools.list();
expect(tools.filter((t) => t.sourceId === "broken_source")).toHaveLength(0);
}),
);
});