feat: user menu dropdown + fix MCP tool schemas #10
@@ -1,3 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import type { db as prodDb } from "../../../db/index.ts";
|
||||
import {
|
||||
createCategory,
|
||||
@@ -24,24 +25,14 @@ export const categoryToolDefinitions = [
|
||||
{
|
||||
name: "list_categories",
|
||||
description: "List all gear categories.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {},
|
||||
},
|
||||
inputSchema: {},
|
||||
},
|
||||
{
|
||||
name: "create_category",
|
||||
description: "Create a new gear category.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
name: { type: "string", description: "Category name" },
|
||||
icon: {
|
||||
type: "string",
|
||||
description: "Icon name (defaults to 'package')",
|
||||
},
|
||||
},
|
||||
required: ["name"],
|
||||
name: z.string().describe("Category name"),
|
||||
icon: z.string().optional().describe("Icon name (defaults to 'package')"),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { fetchImageFromUrl } from "../../services/image.service.ts";
|
||||
|
||||
interface ToolResult {
|
||||
@@ -20,14 +21,9 @@ export const imageToolDefinitions = [
|
||||
description:
|
||||
"Fetch an image from a URL and save it locally. Returns the filename to use with create_item or add_candidate.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
url: {
|
||||
type: "string",
|
||||
description: "URL of the image to fetch (jpeg, png, or webp)",
|
||||
},
|
||||
},
|
||||
required: ["url"],
|
||||
url: z
|
||||
.string()
|
||||
.describe("URL of the image to fetch (jpeg, png, or webp)"),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import type { db as prodDb } from "../../../db/index.ts";
|
||||
import {
|
||||
createItem,
|
||||
@@ -29,24 +30,14 @@ export const itemToolDefinitions = [
|
||||
description:
|
||||
"List all items in the gear collection, optionally filtered by category.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
categoryId: {
|
||||
type: "number",
|
||||
description: "Filter items by category ID",
|
||||
},
|
||||
},
|
||||
categoryId: z.number().optional().describe("Filter items by category ID"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get_item",
|
||||
description: "Get a single item by its ID, including all details.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
id: { type: "number", description: "The item ID" },
|
||||
},
|
||||
required: ["id"],
|
||||
id: z.number().describe("The item ID"),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -54,60 +45,48 @@ export const itemToolDefinitions = [
|
||||
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.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
name: { type: "string", description: "Item name" },
|
||||
categoryId: { type: "number", description: "Category ID" },
|
||||
weightGrams: { type: "number", description: "Weight in grams" },
|
||||
priceCents: { type: "number", description: "Price in cents" },
|
||||
notes: { type: "string", description: "Notes about the item" },
|
||||
productUrl: { type: "string", description: "URL to the product page" },
|
||||
imageFilename: {
|
||||
type: "string",
|
||||
description: "Filename of an uploaded image",
|
||||
},
|
||||
imageSourceUrl: {
|
||||
type: "string",
|
||||
description: "Original URL the image was fetched from",
|
||||
},
|
||||
},
|
||||
required: ["name", "categoryId"],
|
||||
name: z.string().describe("Item name"),
|
||||
categoryId: z.number().describe("Category ID"),
|
||||
weightGrams: z.number().optional().describe("Weight in grams"),
|
||||
priceCents: z.number().optional().describe("Price in cents"),
|
||||
notes: z.string().optional().describe("Notes about the item"),
|
||||
productUrl: z.string().optional().describe("URL to the product page"),
|
||||
imageFilename: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Filename of an uploaded image"),
|
||||
imageSourceUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Original URL the image was fetched from"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update_item",
|
||||
description: "Update an existing item's fields.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
id: { type: "number", description: "The item ID to update" },
|
||||
name: { type: "string", description: "Item name" },
|
||||
categoryId: { type: "number", description: "Category ID" },
|
||||
weightGrams: { type: "number", description: "Weight in grams" },
|
||||
priceCents: { type: "number", description: "Price in cents" },
|
||||
notes: { type: "string", description: "Notes about the item" },
|
||||
productUrl: { type: "string", description: "URL to the product page" },
|
||||
imageFilename: {
|
||||
type: "string",
|
||||
description: "Filename of an uploaded image",
|
||||
},
|
||||
imageSourceUrl: {
|
||||
type: "string",
|
||||
description: "Original URL the image was fetched from",
|
||||
},
|
||||
},
|
||||
required: ["id"],
|
||||
id: z.number().describe("The item ID to update"),
|
||||
name: z.string().optional().describe("Item name"),
|
||||
categoryId: z.number().optional().describe("Category ID"),
|
||||
weightGrams: z.number().optional().describe("Weight in grams"),
|
||||
priceCents: z.number().optional().describe("Price in cents"),
|
||||
notes: z.string().optional().describe("Notes about the item"),
|
||||
productUrl: z.string().optional().describe("URL to the product page"),
|
||||
imageFilename: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Filename of an uploaded image"),
|
||||
imageSourceUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Original URL the image was fetched from"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "delete_item",
|
||||
description: "Delete an item from the gear collection by ID.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
id: { type: "number", description: "The item ID to delete" },
|
||||
},
|
||||
required: ["id"],
|
||||
id: z.number().describe("The item ID to delete"),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import type { db as prodDb } from "../../../db/index.ts";
|
||||
import {
|
||||
createSetup,
|
||||
@@ -28,31 +29,20 @@ export const setupToolDefinitions = [
|
||||
name: "list_setups",
|
||||
description:
|
||||
"List all gear setups with item counts and weight/cost totals.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {},
|
||||
},
|
||||
inputSchema: {},
|
||||
},
|
||||
{
|
||||
name: "get_setup",
|
||||
description: "Get a setup with all its items and details.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
id: { type: "number", description: "Setup ID" },
|
||||
},
|
||||
required: ["id"],
|
||||
id: z.number().describe("Setup ID"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create_setup",
|
||||
description: "Create a new gear setup (e.g. 'Bikepacking weekend').",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
name: { type: "string", description: "Setup name" },
|
||||
},
|
||||
required: ["name"],
|
||||
name: z.string().describe("Setup name"),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -60,17 +50,12 @@ export const setupToolDefinitions = [
|
||||
description:
|
||||
"Update a setup's name and/or replace its item list. Pass itemIds to set exactly which items belong to this setup.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
id: { type: "number", description: "Setup ID" },
|
||||
name: { type: "string", description: "New setup name" },
|
||||
itemIds: {
|
||||
type: "array",
|
||||
items: { type: "number" },
|
||||
description: "Array of item IDs to include in the setup",
|
||||
},
|
||||
},
|
||||
required: ["id"],
|
||||
id: z.number().describe("Setup ID"),
|
||||
name: z.string().optional().describe("New setup name"),
|
||||
itemIds: z
|
||||
.array(z.number())
|
||||
.optional()
|
||||
.describe("Array of item IDs to include in the setup"),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import type { db as prodDb } from "../../../db/index.ts";
|
||||
import {
|
||||
createCandidate,
|
||||
@@ -31,14 +32,12 @@ export const threadToolDefinitions = [
|
||||
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.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
includeResolved: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Include resolved threads (default: false, only active threads)",
|
||||
},
|
||||
},
|
||||
includeResolved: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
"Include resolved threads (default: false, only active threads)",
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -46,11 +45,7 @@ export const threadToolDefinitions = [
|
||||
description:
|
||||
"Get a thread with all its candidates for detailed comparison.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
id: { type: "number", description: "Thread ID" },
|
||||
},
|
||||
required: ["id"],
|
||||
id: z.number().describe("Thread ID"),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -58,15 +53,8 @@ export const threadToolDefinitions = [
|
||||
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.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
description: "Thread name (e.g. 'Handlebar bag')",
|
||||
},
|
||||
categoryId: { type: "number", description: "Category ID" },
|
||||
},
|
||||
required: ["name", "categoryId"],
|
||||
name: z.string().describe("Thread name (e.g. 'Handlebar bag')"),
|
||||
categoryId: z.number().describe("Category ID"),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -74,35 +62,24 @@ export const threadToolDefinitions = [
|
||||
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.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
threadId: { type: "number", description: "Thread ID" },
|
||||
candidateId: {
|
||||
type: "number",
|
||||
description: "ID of the winning candidate",
|
||||
},
|
||||
},
|
||||
required: ["threadId", "candidateId"],
|
||||
threadId: z.number().describe("Thread ID"),
|
||||
candidateId: z.number().describe("ID of the winning candidate"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add_candidate",
|
||||
description: "Add a candidate option to a research thread for comparison.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
threadId: { type: "number", description: "Thread ID" },
|
||||
name: { type: "string", description: "Candidate name" },
|
||||
categoryId: { type: "number", description: "Category ID" },
|
||||
weightGrams: { type: "number", description: "Weight in grams" },
|
||||
priceCents: { type: "number", description: "Price in cents" },
|
||||
notes: { type: "string", description: "Notes" },
|
||||
productUrl: { type: "string", description: "Product URL" },
|
||||
imageFilename: { type: "string", description: "Image filename" },
|
||||
pros: { type: "string", description: "Pros of this candidate" },
|
||||
cons: { type: "string", description: "Cons of this candidate" },
|
||||
},
|
||||
required: ["threadId", "name", "categoryId"],
|
||||
threadId: z.number().describe("Thread ID"),
|
||||
name: z.string().describe("Candidate name"),
|
||||
categoryId: z.number().describe("Category ID"),
|
||||
weightGrams: z.number().optional().describe("Weight in grams"),
|
||||
priceCents: z.number().optional().describe("Price in cents"),
|
||||
notes: z.string().optional().describe("Notes"),
|
||||
productUrl: z.string().optional().describe("Product URL"),
|
||||
imageFilename: z.string().optional().describe("Image filename"),
|
||||
pros: z.string().optional().describe("Pros of this candidate"),
|
||||
cons: z.string().optional().describe("Cons of this candidate"),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -110,36 +87,28 @@ export const threadToolDefinitions = [
|
||||
description:
|
||||
"Update a candidate's details (name, price, pros, cons, etc.).",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
id: { type: "number", description: "Candidate ID" },
|
||||
name: { type: "string", description: "Candidate name" },
|
||||
weightGrams: { type: "number", description: "Weight in grams" },
|
||||
priceCents: { type: "number", description: "Price in cents" },
|
||||
categoryId: { type: "number", description: "Category ID" },
|
||||
notes: { type: "string", description: "Notes" },
|
||||
productUrl: { type: "string", description: "Product URL" },
|
||||
imageFilename: { type: "string", description: "Image filename" },
|
||||
imageSourceUrl: { type: "string", description: "Image source URL" },
|
||||
status: {
|
||||
type: "string",
|
||||
description: "Status: researching, ordered, or arrived",
|
||||
},
|
||||
pros: { type: "string", description: "Pros" },
|
||||
cons: { type: "string", description: "Cons" },
|
||||
},
|
||||
required: ["id"],
|
||||
id: z.number().describe("Candidate ID"),
|
||||
name: z.string().optional().describe("Candidate name"),
|
||||
weightGrams: z.number().optional().describe("Weight in grams"),
|
||||
priceCents: z.number().optional().describe("Price in cents"),
|
||||
categoryId: z.number().optional().describe("Category ID"),
|
||||
notes: z.string().optional().describe("Notes"),
|
||||
productUrl: z.string().optional().describe("Product URL"),
|
||||
imageFilename: z.string().optional().describe("Image filename"),
|
||||
imageSourceUrl: z.string().optional().describe("Image source URL"),
|
||||
status: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Status: researching, ordered, or arrived"),
|
||||
pros: z.string().optional().describe("Pros"),
|
||||
cons: z.string().optional().describe("Cons"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove_candidate",
|
||||
description: "Remove a candidate from a research thread.",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
id: { type: "number", description: "Candidate ID to remove" },
|
||||
},
|
||||
required: ["id"],
|
||||
id: z.number().describe("Candidate ID to remove"),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user