feat(mcp): add snippet CRUD tools to MCP server

- register list_snippets, get_snippet, create_snippet, update_snippet, delete_snippet
- inject SnippetService into McpService and import SnippetModule into McpModule
- cover all five tools with integration tests including error cases
This commit is contained in:
2026-04-20 16:21:52 +03:00
parent 4e494ce4fd
commit ba46789226
5 changed files with 373 additions and 10 deletions
+169
View File
@@ -284,4 +284,173 @@ describe("McpController", () => {
expect(cleared.timeoutSeconds).toBeNull();
});
});
// ── Snippet CRUD tools ────────────────────────────────────────────────────
describe("list_snippets", () => {
it("returns a paginated result with a data array", async () => {
const { status, rpc } = await mcpCall("list_snippets");
expect(status).toBe(200);
const result = rpc.result as { content: { text: string }[] };
const body = JSON.parse(result.content[0].text) as {
data: unknown[];
total: number;
};
expect(Array.isArray(body.data)).toBe(true);
expect(typeof body.total).toBe("number");
});
});
describe("create_snippet", () => {
it("creates a snippet and returns it with alias and title", async () => {
const { status, rpc } = await mcpCall("create_snippet", {
alias: "mcp-test-snippet",
title: "MCP Test Snippet",
description: "Created by integration test",
code: "await page.click('#btn');",
});
expect(status).toBe(200);
const result = rpc.result as { content: { text: string }[] };
const created = JSON.parse(result.content[0].text) as {
id: string;
alias: string;
title: string;
description: string | null;
code: string;
};
expect(created.alias).toBe("mcp-test-snippet");
expect(created.title).toBe("MCP Test Snippet");
expect(created.description).toBe("Created by integration test");
expect(created.code).toBe("await page.click('#btn');");
expect(typeof created.id).toBe("string");
});
it("returns an MCP error when alias already exists", async () => {
await mcpCall("create_snippet", {
alias: "mcp-duplicate-snippet",
title: "First",
code: "return 1;",
});
const { status, rpc } = await mcpCall("create_snippet", {
alias: "mcp-duplicate-snippet",
title: "Second",
code: "return 2;",
});
expect(status).toBe(200);
const result = rpc.result as { isError: boolean };
expect(result.isError).toBe(true);
});
});
describe("get_snippet", () => {
it("returns the created snippet by id", async () => {
const createRpc = (
await mcpCall("create_snippet", {
alias: "mcp-get-snippet",
title: "Get Me",
code: "return 42;",
})
).rpc;
const created = JSON.parse(
(createRpc.result as { content: { text: string }[] }).content[0].text,
) as { id: string };
const { status, rpc } = await mcpCall("get_snippet", { id: created.id });
expect(status).toBe(200);
const result = rpc.result as { content: { text: string }[] };
const fetched = JSON.parse(result.content[0].text) as {
id: string;
alias: string;
};
expect(fetched.id).toBe(created.id);
expect(fetched.alias).toBe("mcp-get-snippet");
});
it("returns an MCP error for a non-existent snippet id", async () => {
const { status, rpc } = await mcpCall("get_snippet", {
id: "00000000-0000-0000-0000-000000000000",
});
expect(status).toBe(200);
const result = rpc.result as { isError: boolean };
expect(result.isError).toBe(true);
});
});
describe("update_snippet", () => {
it("updates alias, title, and code of an existing snippet", async () => {
const createRpc = (
await mcpCall("create_snippet", {
alias: "mcp-upd-snippet-orig",
title: "Original Title",
code: "return 1;",
})
).rpc;
const created = JSON.parse(
(createRpc.result as { content: { text: string }[] }).content[0].text,
) as { id: string };
const { status, rpc } = await mcpCall("update_snippet", {
id: created.id,
alias: "mcp-upd-snippet-new",
title: "Updated Title",
code: "return 2;",
});
expect(status).toBe(200);
const result = rpc.result as { content: { text: string }[] };
const updated = JSON.parse(result.content[0].text) as {
alias: string;
title: string;
code: string;
};
expect(updated.alias).toBe("mcp-upd-snippet-new");
expect(updated.title).toBe("Updated Title");
expect(updated.code).toBe("return 2;");
});
it("returns an MCP error for a non-existent snippet id", async () => {
const { status, rpc } = await mcpCall("update_snippet", {
id: "00000000-0000-0000-0000-000000000000",
title: "Ghost",
});
expect(status).toBe(200);
const result = rpc.result as { isError: boolean };
expect(result.isError).toBe(true);
});
});
describe("delete_snippet", () => {
it("deletes an existing snippet and confirms deletion", async () => {
const createRpc = (
await mcpCall("create_snippet", {
alias: "mcp-del-snippet",
title: "Delete Me",
code: "return 0;",
})
).rpc;
const created = JSON.parse(
(createRpc.result as { content: { text: string }[] }).content[0].text,
) as { id: string };
const { status, rpc } = await mcpCall("delete_snippet", {
id: created.id,
});
expect(status).toBe(200);
const result = rpc.result as { content: { text: string }[] };
expect(result.content[0].text).toContain(created.id);
// confirm gone
const getResult = (await mcpCall("get_snippet", { id: created.id }))
.rpc.result as { isError: boolean };
expect(getResult.isError).toBe(true);
});
it("returns an MCP error for a non-existent snippet id", async () => {
const { status, rpc } = await mcpCall("delete_snippet", {
id: "00000000-0000-0000-0000-000000000000",
});
expect(status).toBe(200);
const result = rpc.result as { isError: boolean };
expect(result.isError).toBe(true);
});
});
});