feat(25-02): add MCP catalog tools upsert_catalog_item and bulk_upsert_catalog
- New catalog.ts with catalogToolDefinitions and registerCatalogTools - upsert_catalog_item: single item upsert with full attribution fields (SEED-03) - bulk_upsert_catalog: batch upsert up to 100 items with created/updated counts - Registered in createMcpServer after image tools - 6 new MCP catalog tool tests passing
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { getCollectionSummary } from "../../src/server/mcp/resources/collection.ts";
|
||||
import { registerCatalogTools } from "../../src/server/mcp/tools/catalog.ts";
|
||||
import { registerCategoryTools } from "../../src/server/mcp/tools/categories.ts";
|
||||
import { registerItemTools } from "../../src/server/mcp/tools/items.ts";
|
||||
import { registerSetupTools } from "../../src/server/mcp/tools/setups.ts";
|
||||
@@ -252,6 +253,112 @@ describe("MCP Collection Summary Resource", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("MCP Catalog Tools", () => {
|
||||
test("upsert_catalog_item creates a new global item with created=true", async () => {
|
||||
const { db } = await createTestDb();
|
||||
const tools = registerCatalogTools(db);
|
||||
const result = await tools.upsert_catalog_item({
|
||||
brand: "Revelate Designs",
|
||||
model: "Terrapin System",
|
||||
weightGrams: 235,
|
||||
priceCents: 16500,
|
||||
});
|
||||
const data = parseResult(result);
|
||||
expect(data.brand).toBe("Revelate Designs");
|
||||
expect(data.model).toBe("Terrapin System");
|
||||
expect(data.created).toBe(true);
|
||||
expect(data.id).toBeDefined();
|
||||
});
|
||||
|
||||
test("upsert_catalog_item updates existing item on brand+model match", async () => {
|
||||
const { db } = await createTestDb();
|
||||
const tools = registerCatalogTools(db);
|
||||
|
||||
// Create initial item
|
||||
await tools.upsert_catalog_item({
|
||||
brand: "Apidura",
|
||||
model: "Handlebar Pack",
|
||||
});
|
||||
|
||||
// Update it
|
||||
const result = await tools.upsert_catalog_item({
|
||||
brand: "Apidura",
|
||||
model: "Handlebar Pack",
|
||||
description: "Updated description",
|
||||
weightGrams: 120,
|
||||
});
|
||||
const data = parseResult(result);
|
||||
expect(data.created).toBe(false);
|
||||
expect(data.description).toBe("Updated description");
|
||||
expect(data.weightGrams).toBe(120);
|
||||
});
|
||||
|
||||
test("upsert_catalog_item includes attribution fields in result (SEED-03)", async () => {
|
||||
const { db } = await createTestDb();
|
||||
const tools = registerCatalogTools(db);
|
||||
|
||||
const result = await tools.upsert_catalog_item({
|
||||
brand: "MSR",
|
||||
model: "PocketRocket 2",
|
||||
sourceUrl: "https://www.cascadedesigns.com/msr/pocket-rocket-2",
|
||||
imageCredit: "MSR Photography",
|
||||
imageSourceUrl: "https://cdn.cascadedesigns.com/images/pocket-rocket-2.jpg",
|
||||
});
|
||||
const data = parseResult(result);
|
||||
expect(data.sourceUrl).toBe("https://www.cascadedesigns.com/msr/pocket-rocket-2");
|
||||
expect(data.imageCredit).toBe("MSR Photography");
|
||||
expect(data.imageSourceUrl).toBe("https://cdn.cascadedesigns.com/images/pocket-rocket-2.jpg");
|
||||
});
|
||||
|
||||
test("bulk_upsert_catalog processes array and returns created/updated counts", async () => {
|
||||
const { db } = await createTestDb();
|
||||
const tools = registerCatalogTools(db);
|
||||
|
||||
const result = await tools.bulk_upsert_catalog({
|
||||
items: [
|
||||
{ brand: "Revelate Designs", model: "Terrapin System" },
|
||||
{ brand: "Apidura", model: "Handlebar Pack" },
|
||||
{ brand: "MSR", model: "PocketRocket 2" },
|
||||
],
|
||||
});
|
||||
const data = parseResult(result);
|
||||
expect(data.created).toBe(3);
|
||||
expect(data.updated).toBe(0);
|
||||
expect(data.totalProcessed).toBe(3);
|
||||
expect(data.items).toHaveLength(3);
|
||||
});
|
||||
|
||||
test("bulk_upsert_catalog returns totalProcessed matching input length", async () => {
|
||||
const { db } = await createTestDb();
|
||||
const tools = registerCatalogTools(db);
|
||||
|
||||
// Pre-create one item
|
||||
await tools.upsert_catalog_item({ brand: "Revelate Designs", model: "Terrapin System" });
|
||||
|
||||
const result = await tools.bulk_upsert_catalog({
|
||||
items: [
|
||||
{ brand: "Revelate Designs", model: "Terrapin System" },
|
||||
{ brand: "Apidura", model: "Handlebar Pack" },
|
||||
],
|
||||
});
|
||||
const data = parseResult(result);
|
||||
expect(data.totalProcessed).toBe(2);
|
||||
expect(data.created).toBe(1);
|
||||
expect(data.updated).toBe(1);
|
||||
});
|
||||
|
||||
test("catalog tool definitions include attribution fields in inputSchema", () => {
|
||||
const { catalogToolDefinitions } = require("../../src/server/mcp/tools/catalog.ts");
|
||||
const upsertDef = catalogToolDefinitions.find(
|
||||
(d: { name: string }) => d.name === "upsert_catalog_item",
|
||||
);
|
||||
expect(upsertDef).toBeDefined();
|
||||
expect(upsertDef.inputSchema.sourceUrl).toBeDefined();
|
||||
expect(upsertDef.inputSchema.imageCredit).toBeDefined();
|
||||
expect(upsertDef.inputSchema.imageSourceUrl).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("MCP Cross-User Isolation", () => {
|
||||
test("user 2 cannot see user 1's items via MCP tools", async () => {
|
||||
const { db, userId } = await createTestDb();
|
||||
|
||||
Reference in New Issue
Block a user