fix: convert MCP tool schemas from JSON Schema to Zod for SDK v1.29.0
The MCP SDK v1.29.0 changed server.tool() to require Zod schemas (raw shapes) instead of plain JSON Schema objects. The old format triggered "expected a Zod schema or ToolAnnotations" errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { z } from "zod";
|
||||||
import type { db as prodDb } from "../../../db/index.ts";
|
import type { db as prodDb } from "../../../db/index.ts";
|
||||||
import {
|
import {
|
||||||
createCategory,
|
createCategory,
|
||||||
@@ -24,24 +25,14 @@ export const categoryToolDefinitions = [
|
|||||||
{
|
{
|
||||||
name: "list_categories",
|
name: "list_categories",
|
||||||
description: "List all gear categories.",
|
description: "List all gear categories.",
|
||||||
inputSchema: {
|
inputSchema: {},
|
||||||
type: "object" as const,
|
|
||||||
properties: {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "create_category",
|
name: "create_category",
|
||||||
description: "Create a new gear category.",
|
description: "Create a new gear category.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
name: z.string().describe("Category name"),
|
||||||
properties: {
|
icon: z.string().optional().describe("Icon name (defaults to 'package')"),
|
||||||
name: { type: "string", description: "Category name" },
|
|
||||||
icon: {
|
|
||||||
type: "string",
|
|
||||||
description: "Icon name (defaults to 'package')",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["name"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { z } from "zod";
|
||||||
import { fetchImageFromUrl } from "../../services/image.service.ts";
|
import { fetchImageFromUrl } from "../../services/image.service.ts";
|
||||||
|
|
||||||
interface ToolResult {
|
interface ToolResult {
|
||||||
@@ -20,14 +21,9 @@ export const imageToolDefinitions = [
|
|||||||
description:
|
description:
|
||||||
"Fetch an image from a URL and save it locally. Returns the filename to use with create_item or add_candidate.",
|
"Fetch an image from a URL and save it locally. Returns the filename to use with create_item or add_candidate.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
url: z
|
||||||
properties: {
|
.string()
|
||||||
url: {
|
.describe("URL of the image to fetch (jpeg, png, or webp)"),
|
||||||
type: "string",
|
|
||||||
description: "URL of the image to fetch (jpeg, png, or webp)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["url"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { z } from "zod";
|
||||||
import type { db as prodDb } from "../../../db/index.ts";
|
import type { db as prodDb } from "../../../db/index.ts";
|
||||||
import {
|
import {
|
||||||
createItem,
|
createItem,
|
||||||
@@ -29,24 +30,14 @@ export const itemToolDefinitions = [
|
|||||||
description:
|
description:
|
||||||
"List all items in the gear collection, optionally filtered by category.",
|
"List all items in the gear collection, optionally filtered by category.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
categoryId: z.number().optional().describe("Filter items by category ID"),
|
||||||
properties: {
|
|
||||||
categoryId: {
|
|
||||||
type: "number",
|
|
||||||
description: "Filter items by category ID",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "get_item",
|
name: "get_item",
|
||||||
description: "Get a single item by its ID, including all details.",
|
description: "Get a single item by its ID, including all details.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
id: z.number().describe("The item ID"),
|
||||||
properties: {
|
|
||||||
id: { type: "number", description: "The item ID" },
|
|
||||||
},
|
|
||||||
required: ["id"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -54,60 +45,48 @@ export const itemToolDefinitions = [
|
|||||||
description:
|
description:
|
||||||
"Add a new item to the gear collection. Use this for items you've already decided on. For items you're still researching, use create_thread instead.",
|
"Add a new item to the gear collection. Use this for items you've already decided on. For items you're still researching, use create_thread instead.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
name: z.string().describe("Item name"),
|
||||||
properties: {
|
categoryId: z.number().describe("Category ID"),
|
||||||
name: { type: "string", description: "Item name" },
|
weightGrams: z.number().optional().describe("Weight in grams"),
|
||||||
categoryId: { type: "number", description: "Category ID" },
|
priceCents: z.number().optional().describe("Price in cents"),
|
||||||
weightGrams: { type: "number", description: "Weight in grams" },
|
notes: z.string().optional().describe("Notes about the item"),
|
||||||
priceCents: { type: "number", description: "Price in cents" },
|
productUrl: z.string().optional().describe("URL to the product page"),
|
||||||
notes: { type: "string", description: "Notes about the item" },
|
imageFilename: z
|
||||||
productUrl: { type: "string", description: "URL to the product page" },
|
.string()
|
||||||
imageFilename: {
|
.optional()
|
||||||
type: "string",
|
.describe("Filename of an uploaded image"),
|
||||||
description: "Filename of an uploaded image",
|
imageSourceUrl: z
|
||||||
},
|
.string()
|
||||||
imageSourceUrl: {
|
.optional()
|
||||||
type: "string",
|
.describe("Original URL the image was fetched from"),
|
||||||
description: "Original URL the image was fetched from",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["name", "categoryId"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "update_item",
|
name: "update_item",
|
||||||
description: "Update an existing item's fields.",
|
description: "Update an existing item's fields.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
id: z.number().describe("The item ID to update"),
|
||||||
properties: {
|
name: z.string().optional().describe("Item name"),
|
||||||
id: { type: "number", description: "The item ID to update" },
|
categoryId: z.number().optional().describe("Category ID"),
|
||||||
name: { type: "string", description: "Item name" },
|
weightGrams: z.number().optional().describe("Weight in grams"),
|
||||||
categoryId: { type: "number", description: "Category ID" },
|
priceCents: z.number().optional().describe("Price in cents"),
|
||||||
weightGrams: { type: "number", description: "Weight in grams" },
|
notes: z.string().optional().describe("Notes about the item"),
|
||||||
priceCents: { type: "number", description: "Price in cents" },
|
productUrl: z.string().optional().describe("URL to the product page"),
|
||||||
notes: { type: "string", description: "Notes about the item" },
|
imageFilename: z
|
||||||
productUrl: { type: "string", description: "URL to the product page" },
|
.string()
|
||||||
imageFilename: {
|
.optional()
|
||||||
type: "string",
|
.describe("Filename of an uploaded image"),
|
||||||
description: "Filename of an uploaded image",
|
imageSourceUrl: z
|
||||||
},
|
.string()
|
||||||
imageSourceUrl: {
|
.optional()
|
||||||
type: "string",
|
.describe("Original URL the image was fetched from"),
|
||||||
description: "Original URL the image was fetched from",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["id"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "delete_item",
|
name: "delete_item",
|
||||||
description: "Delete an item from the gear collection by ID.",
|
description: "Delete an item from the gear collection by ID.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
id: z.number().describe("The item ID to delete"),
|
||||||
properties: {
|
|
||||||
id: { type: "number", description: "The item ID to delete" },
|
|
||||||
},
|
|
||||||
required: ["id"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { z } from "zod";
|
||||||
import type { db as prodDb } from "../../../db/index.ts";
|
import type { db as prodDb } from "../../../db/index.ts";
|
||||||
import {
|
import {
|
||||||
createSetup,
|
createSetup,
|
||||||
@@ -28,31 +29,20 @@ export const setupToolDefinitions = [
|
|||||||
name: "list_setups",
|
name: "list_setups",
|
||||||
description:
|
description:
|
||||||
"List all gear setups with item counts and weight/cost totals.",
|
"List all gear setups with item counts and weight/cost totals.",
|
||||||
inputSchema: {
|
inputSchema: {},
|
||||||
type: "object" as const,
|
|
||||||
properties: {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "get_setup",
|
name: "get_setup",
|
||||||
description: "Get a setup with all its items and details.",
|
description: "Get a setup with all its items and details.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
id: z.number().describe("Setup ID"),
|
||||||
properties: {
|
|
||||||
id: { type: "number", description: "Setup ID" },
|
|
||||||
},
|
|
||||||
required: ["id"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "create_setup",
|
name: "create_setup",
|
||||||
description: "Create a new gear setup (e.g. 'Bikepacking weekend').",
|
description: "Create a new gear setup (e.g. 'Bikepacking weekend').",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
name: z.string().describe("Setup name"),
|
||||||
properties: {
|
|
||||||
name: { type: "string", description: "Setup name" },
|
|
||||||
},
|
|
||||||
required: ["name"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -60,17 +50,12 @@ export const setupToolDefinitions = [
|
|||||||
description:
|
description:
|
||||||
"Update a setup's name and/or replace its item list. Pass itemIds to set exactly which items belong to this setup.",
|
"Update a setup's name and/or replace its item list. Pass itemIds to set exactly which items belong to this setup.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
id: z.number().describe("Setup ID"),
|
||||||
properties: {
|
name: z.string().optional().describe("New setup name"),
|
||||||
id: { type: "number", description: "Setup ID" },
|
itemIds: z
|
||||||
name: { type: "string", description: "New setup name" },
|
.array(z.number())
|
||||||
itemIds: {
|
.optional()
|
||||||
type: "array",
|
.describe("Array of item IDs to include in the setup"),
|
||||||
items: { type: "number" },
|
|
||||||
description: "Array of item IDs to include in the setup",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["id"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { z } from "zod";
|
||||||
import type { db as prodDb } from "../../../db/index.ts";
|
import type { db as prodDb } from "../../../db/index.ts";
|
||||||
import {
|
import {
|
||||||
createCandidate,
|
createCandidate,
|
||||||
@@ -31,14 +32,12 @@ export const threadToolDefinitions = [
|
|||||||
description:
|
description:
|
||||||
"List research threads. Threads are the recommended way to evaluate gear purchases — each thread tracks multiple candidates for a single gear slot, making it easy to compare options before committing.",
|
"List research threads. Threads are the recommended way to evaluate gear purchases — each thread tracks multiple candidates for a single gear slot, making it easy to compare options before committing.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
includeResolved: z
|
||||||
properties: {
|
.boolean()
|
||||||
includeResolved: {
|
.optional()
|
||||||
type: "boolean",
|
.describe(
|
||||||
description:
|
|
||||||
"Include resolved threads (default: false, only active threads)",
|
"Include resolved threads (default: false, only active threads)",
|
||||||
},
|
),
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -46,11 +45,7 @@ export const threadToolDefinitions = [
|
|||||||
description:
|
description:
|
||||||
"Get a thread with all its candidates for detailed comparison.",
|
"Get a thread with all its candidates for detailed comparison.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
id: z.number().describe("Thread ID"),
|
||||||
properties: {
|
|
||||||
id: { type: "number", description: "Thread ID" },
|
|
||||||
},
|
|
||||||
required: ["id"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -58,15 +53,8 @@ export const threadToolDefinitions = [
|
|||||||
description:
|
description:
|
||||||
"Start a new research thread for a gear slot. This is the preferred workflow: create a thread, add candidates with pros/cons/prices, compare them, then resolve the thread to add the winner to your collection.",
|
"Start a new research thread for a gear slot. This is the preferred workflow: create a thread, add candidates with pros/cons/prices, compare them, then resolve the thread to add the winner to your collection.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
name: z.string().describe("Thread name (e.g. 'Handlebar bag')"),
|
||||||
properties: {
|
categoryId: z.number().describe("Category ID"),
|
||||||
name: {
|
|
||||||
type: "string",
|
|
||||||
description: "Thread name (e.g. 'Handlebar bag')",
|
|
||||||
},
|
|
||||||
categoryId: { type: "number", description: "Category ID" },
|
|
||||||
},
|
|
||||||
required: ["name", "categoryId"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -74,35 +62,24 @@ export const threadToolDefinitions = [
|
|||||||
description:
|
description:
|
||||||
"Resolve a research thread by picking the winning candidate. The winner is automatically added to the gear collection as a new item, and the thread is marked as resolved.",
|
"Resolve a research thread by picking the winning candidate. The winner is automatically added to the gear collection as a new item, and the thread is marked as resolved.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
threadId: z.number().describe("Thread ID"),
|
||||||
properties: {
|
candidateId: z.number().describe("ID of the winning candidate"),
|
||||||
threadId: { type: "number", description: "Thread ID" },
|
|
||||||
candidateId: {
|
|
||||||
type: "number",
|
|
||||||
description: "ID of the winning candidate",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["threadId", "candidateId"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add_candidate",
|
name: "add_candidate",
|
||||||
description: "Add a candidate option to a research thread for comparison.",
|
description: "Add a candidate option to a research thread for comparison.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
threadId: z.number().describe("Thread ID"),
|
||||||
properties: {
|
name: z.string().describe("Candidate name"),
|
||||||
threadId: { type: "number", description: "Thread ID" },
|
categoryId: z.number().describe("Category ID"),
|
||||||
name: { type: "string", description: "Candidate name" },
|
weightGrams: z.number().optional().describe("Weight in grams"),
|
||||||
categoryId: { type: "number", description: "Category ID" },
|
priceCents: z.number().optional().describe("Price in cents"),
|
||||||
weightGrams: { type: "number", description: "Weight in grams" },
|
notes: z.string().optional().describe("Notes"),
|
||||||
priceCents: { type: "number", description: "Price in cents" },
|
productUrl: z.string().optional().describe("Product URL"),
|
||||||
notes: { type: "string", description: "Notes" },
|
imageFilename: z.string().optional().describe("Image filename"),
|
||||||
productUrl: { type: "string", description: "Product URL" },
|
pros: z.string().optional().describe("Pros of this candidate"),
|
||||||
imageFilename: { type: "string", description: "Image filename" },
|
cons: z.string().optional().describe("Cons of this candidate"),
|
||||||
pros: { type: "string", description: "Pros of this candidate" },
|
|
||||||
cons: { type: "string", description: "Cons of this candidate" },
|
|
||||||
},
|
|
||||||
required: ["threadId", "name", "categoryId"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -110,36 +87,28 @@ export const threadToolDefinitions = [
|
|||||||
description:
|
description:
|
||||||
"Update a candidate's details (name, price, pros, cons, etc.).",
|
"Update a candidate's details (name, price, pros, cons, etc.).",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
id: z.number().describe("Candidate ID"),
|
||||||
properties: {
|
name: z.string().optional().describe("Candidate name"),
|
||||||
id: { type: "number", description: "Candidate ID" },
|
weightGrams: z.number().optional().describe("Weight in grams"),
|
||||||
name: { type: "string", description: "Candidate name" },
|
priceCents: z.number().optional().describe("Price in cents"),
|
||||||
weightGrams: { type: "number", description: "Weight in grams" },
|
categoryId: z.number().optional().describe("Category ID"),
|
||||||
priceCents: { type: "number", description: "Price in cents" },
|
notes: z.string().optional().describe("Notes"),
|
||||||
categoryId: { type: "number", description: "Category ID" },
|
productUrl: z.string().optional().describe("Product URL"),
|
||||||
notes: { type: "string", description: "Notes" },
|
imageFilename: z.string().optional().describe("Image filename"),
|
||||||
productUrl: { type: "string", description: "Product URL" },
|
imageSourceUrl: z.string().optional().describe("Image source URL"),
|
||||||
imageFilename: { type: "string", description: "Image filename" },
|
status: z
|
||||||
imageSourceUrl: { type: "string", description: "Image source URL" },
|
.string()
|
||||||
status: {
|
.optional()
|
||||||
type: "string",
|
.describe("Status: researching, ordered, or arrived"),
|
||||||
description: "Status: researching, ordered, or arrived",
|
pros: z.string().optional().describe("Pros"),
|
||||||
},
|
cons: z.string().optional().describe("Cons"),
|
||||||
pros: { type: "string", description: "Pros" },
|
|
||||||
cons: { type: "string", description: "Cons" },
|
|
||||||
},
|
|
||||||
required: ["id"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "remove_candidate",
|
name: "remove_candidate",
|
||||||
description: "Remove a candidate from a research thread.",
|
description: "Remove a candidate from a research thread.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object" as const,
|
id: z.number().describe("Candidate ID to remove"),
|
||||||
properties: {
|
|
||||||
id: { type: "number", description: "Candidate ID to remove" },
|
|
||||||
},
|
|
||||||
required: ["id"],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user