diff --git a/cli/mcp/tools.ts b/cli/mcp/tools.ts index 4a75dd712e..cbbbd745c9 100644 --- a/cli/mcp/tools.ts +++ b/cli/mcp/tools.ts @@ -35,10 +35,14 @@ export function setServerStartTime(time: number): void { const getErrorsInput = z.object({ type: z.enum(["compile", "runtime", "bundle", "hmr", "module"]).optional().describe( - "Filter by error type", + "Filter by error type. Example: 'compile'. Omit to return all types.", + ), + file: z.string().optional().describe( + "Filter by file path. Example: 'app/page.tsx'. Omit to return errors from all files.", + ), + limit: z.number().optional().default(50).describe( + "Maximum number of errors to return. Defaults to 50.", ), - file: z.string().optional().describe("Filter by file path"), - limit: z.number().optional().default(50).describe("Maximum number of errors to return"), }); type GetErrorsInput = z.infer; @@ -48,7 +52,7 @@ export const vfGetErrors: MCPTool = { title: "Get Errors", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, description: - "Get compilation, runtime, and build errors from the dev server. Use this to debug issues with your code.", + "Use this when you need to check for compilation, runtime, bundle, HMR, or module errors in the dev server. Returns error details including file path, line number, and message. Do not use for server logs — use vf_get_logs instead.", inputSchema: getErrorsInput, execute: async (input) => { const errors = getErrorCollector().getAll({ @@ -62,13 +66,21 @@ export const vfGetErrors: MCPTool = { }; const getLogsInput = z.object({ - level: z.enum(["debug", "info", "warn", "error"]).optional().describe("Filter by log level"), + level: z.enum(["debug", "info", "warn", "error"]).optional().describe( + "Filter by log level. Example: 'error'. Omit to return all levels.", + ), source: z.string().optional().describe( - "Filter by log source (e.g., 'server', 'hmr', 'transform')", + "Filter by log source. Example: 'server', 'hmr', 'transform'. Omit to return all sources.", + ), + pattern: z.string().optional().describe( + "Filter by pattern (case-insensitive substring match). Example: 'timeout'.", + ), + limit: z.number().optional().default(100).describe( + "Maximum number of log entries to return. Defaults to 100.", + ), + since: z.number().optional().describe( + "Only return logs after this Unix timestamp in milliseconds.", ), - pattern: z.string().optional().describe("Filter by pattern (case-insensitive substring match)"), - limit: z.number().optional().default(100).describe("Maximum number of log entries to return"), - since: z.number().optional().describe("Only return logs after this timestamp"), }); type GetLogsInput = z.infer; @@ -78,7 +90,7 @@ export const vfGetLogs: MCPTool = { title: "Get Logs", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, description: - "Get recent server logs. Use this to understand what the server is doing and debug runtime issues.", + "Use this when you need to inspect server logs to understand runtime behavior or debug request handling. Returns log entries with timestamp, level, source, and message. Do not use for build/compile errors — use vf_get_errors instead.", inputSchema: getLogsInput, execute: async (input) => { return getLogBuffer().query({ @@ -93,7 +105,7 @@ export const vfGetLogs: MCPTool = { const clearCacheInput = z.object({ type: z.enum(["all", "modules", "mdx"]).optional().default("all").describe( - "Type of cache to clear", + "Type of cache to clear. Example: 'modules'. Defaults to 'all'.", ), }); @@ -114,7 +126,7 @@ export const vfClearCache: MCPTool = { openWorldHint: false, }, description: - "Clear module and build caches. Use this when changes aren't being reflected or to force a rebuild.", + "Use this when the dev server shows stale modules or MDX content. Returns the list of cleared cache directories. Do not use to fix code errors — those require code changes.", inputSchema: clearCacheInput, execute: async (input) => { const fs = createFileSystem(); @@ -156,7 +168,8 @@ export function createVfGetStatus( name: "vf_get_status", title: "Server Status", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, - description: "Get the current status of the dev server including error counts and uptime.", + description: + "Use this when you need a quick summary of the dev server's uptime, error counts, and warning counts. Note: always reports running=true when the MCP server is reachable. Do not use for detailed error info — use vf_get_errors instead.", inputSchema: getStatusInput, execute: async () => { const errors = getErrorCollector(); @@ -180,9 +193,11 @@ export function createVfGetStatus( export const vfGetStatus = createVfGetStatus(); const clearErrorsInput = z.object({ - file: z.string().optional().describe("Clear errors for a specific file only"), + file: z.string().optional().describe( + "Clear errors for a specific file only. Example: 'app/page.tsx'. Omit to clear all files.", + ), type: z.enum(["compile", "runtime", "bundle", "hmr", "module"]).optional().describe( - "Clear errors of a specific type only", + "Clear errors of a specific type only. Example: 'compile'. Omit to clear all types.", ), }); @@ -201,7 +216,8 @@ export const vfClearErrors: MCPTool = { idempotentHint: true, openWorldHint: false, }, - description: "Clear errors from the error collector. Useful after fixing issues.", + description: + "Use this when you need to clear accumulated errors from the error collector, optionally filtering by file or type. Returns the number of cleared errors. Do not use for viewing errors — use vf_get_errors instead.", inputSchema: clearErrorsInput, execute: async (input) => { const collector = getErrorCollector(); diff --git a/cli/mcp/tools/catalog-tools.ts b/cli/mcp/tools/catalog-tools.ts index 9fc06f1a18..3b9c527457 100644 --- a/cli/mcp/tools/catalog-tools.ts +++ b/cli/mcp/tools/catalog-tools.ts @@ -337,7 +337,7 @@ export const vfListExamples: MCPTool = { title: "List Examples", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, description: - "List example projects that demonstrate Veryfront features. Use these as references or starting points for new projects.", + "Use this when you need to browse example projects that demonstrate Veryfront features and integrations. Returns an array of example info with name, description, and category. Do not use for project templates — use vf_list_templates instead.", inputSchema: listExamplesInput, execute: () => Promise.resolve(EXAMPLES), }; @@ -355,7 +355,7 @@ export const vfListTemplates: MCPTool = { title: "List Templates", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }, // openWorldHint: templates come from remote catalog API description: - "List available project templates. Use this to help users choose the right starting point for their project.", + "Use this when you need to list available project templates for creating new projects. Returns an array of template info with name and description. Do not use for example projects — use vf_list_examples instead.", inputSchema: listTemplatesInput, execute: () => Promise.resolve(TEMPLATES), }; @@ -379,7 +379,7 @@ export const vfListIntegrations: MCPTool { const { category } = input; @@ -401,7 +401,7 @@ export const vfListUsecases: MCPTool = { title: "List Use Cases", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, description: - "List pre-configured use-case templates. Each includes recommended integrations and UI layout for common scenarios.", + "Use this when you need to browse pre-configured use-case templates with recommended integrations and UI layouts. Returns an array of use-case info with name, integrations, and layout. Do not use for raw templates — use vf_list_templates instead.", inputSchema: listUsecasesInput, execute: () => Promise.resolve(USECASES), }; @@ -449,7 +449,7 @@ export const vfCreateProject: MCPTool = title: "Create Project", annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }, description: - "Create a new Veryfront project from a template. This is the MCP equivalent of 'veryfront init'. Returns the project directory and next steps.", + "Use this when you need to create a new Veryfront project from a template. Returns the project directory and next steps. Do not use for scaffolding individual files — use vf_scaffold instead.", inputSchema: createProjectInput, execute: (input) => withSpan( diff --git a/cli/mcp/tools/dev-tools.ts b/cli/mcp/tools/dev-tools.ts index d8e85ac88d..05f9cd5382 100644 --- a/cli/mcp/tools/dev-tools.ts +++ b/cli/mcp/tools/dev-tools.ts @@ -18,7 +18,7 @@ const hotReloadInput = z.object({ file: z .string() .optional() - .describe("Specific file to trigger reload for (optional - reloads all if not specified)"), + .describe("Specific file to trigger reload for. Example: 'app/page.tsx'. Omit to reload all."), }); type HotReloadInput = z.infer; @@ -38,7 +38,7 @@ export const vfHotReload: MCPTool = { openWorldHint: false, }, description: - "Trigger a hot reload of the dev server. Use after making changes to see them instantly.", + "Use this when you need to signal that a hot reload should occur. Note: currently a no-op stub that returns success without triggering an actual reload. For file-level HMR that sends a WebSocket update, use vf_trigger_hmr instead.", inputSchema: hotReloadInput, execute: () => Promise.resolve({ @@ -80,7 +80,7 @@ export const vfGetDebugContext: MCPTool withSpan( @@ -121,7 +121,7 @@ export const vfGetDebugContext: MCPTool = { openWorldHint: false, }, description: - "Trigger Hot Module Replacement for a specific file. The browser will update without a full reload.", + "Use this when you need to force an HMR update for a specific file path. Sends a WebSocket reload notification to connected browsers. Returns success status and active listener count. Do not use if no browser is connected — check vf_get_flywheel_status first.", inputSchema: triggerHmrInput, execute: (input) => { const metrics = ReloadNotifier.getMetrics(); @@ -170,7 +170,7 @@ export const vfTriggerHmr: MCPTool = { const previewRouteInput = z.object({ route: z.string().startsWith("/", "Route must start with /").describe( - "Route path to preview (e.g., '/', '/dashboard', '/api/users')", + "Route path to preview. Example: '/', '/dashboard', '/api/users'.", ), port: z.number().int().min(1).max(65535).optional().default(8080).describe( "Dev server port (defaults to 8080)", @@ -179,7 +179,9 @@ const previewRouteInput = z.object({ .enum(["html", "json", "status"]) .optional() .default("status") - .describe("Output format: html (full page), json (API response), status (just HTTP status)"), + .describe( + "Output format: 'html' for full page, 'json' for API response, 'status' for just HTTP status. Defaults to 'status'.", + ), }); type PreviewRouteInput = z.infer; @@ -199,7 +201,7 @@ export const vfPreviewRoute: MCPTool = { title: "Preview Route", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, description: - "Preview a route by making a request to the dev server. Returns the rendered output, HTTP status, and render time. Perfect for testing changes instantly.", + "Use this when you need to test-render a route and inspect the response. Returns rendered output, HTTP status, and render time. Note: API routes may have side effects. Do not use for listing routes — use vf_list_routes instead.", inputSchema: previewRouteInput, execute: (input) => withSpan( @@ -282,7 +284,7 @@ export const vfWaitForReady: MCPTool = { title: "Wait for Ready", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, description: - "Wait for the server to be ready by polling the health endpoint. Use this after starting the server to ensure it's accepting requests.", + "Use this when you need to wait for the dev server to become ready after restart. Polls the health endpoint until responsive. Returns success status and elapsed time. Do not use for error counts or uptime — use vf_get_status instead.", inputSchema: waitForReadyInput, execute: (input) => withSpan( @@ -372,7 +374,7 @@ export const vfGetFlywheelStatus: MCPTool withSpan( diff --git a/cli/mcp/tools/introspection-tools.ts b/cli/mcp/tools/introspection-tools.ts index cb5bbed44b..83e0c35dce 100644 --- a/cli/mcp/tools/introspection-tools.ts +++ b/cli/mcp/tools/introspection-tools.ts @@ -14,7 +14,7 @@ const vfGetSchema: MCPTool = { title: "Get CLI Schema", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, description: - "Get the CLI command schema for discovering available commands, arguments, and flags.", + "Use this when you need to discover available CLI commands, their arguments, and flags. Returns the command schema as JSON. Do not use for project info — use vf_get_project_info instead.", inputSchema: getSchemaInput, execute: async (input: { command?: string; category?: string }) => { if (input.command) { @@ -30,7 +30,8 @@ const vfGetProjectInfo: MCPTool = { name: "vf_get_project_info", title: "Get Project Info", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, - description: "Get project metadata including project slug, version, and environment.", + description: + "Use this when you need project metadata including project slug, version, and environment. Returns slug, version, and environment config. Do not use for CLI commands — use vf_get_schema instead.", inputSchema: getProjectInfoInput, execute: async () => { const { getEnvironmentConfig } = await import("veryfront/config"); diff --git a/cli/mcp/tools/project-tools.ts b/cli/mcp/tools/project-tools.ts index 75758dc9e8..44135195f0 100644 --- a/cli/mcp/tools/project-tools.ts +++ b/cli/mcp/tools/project-tools.ts @@ -40,7 +40,7 @@ export const vfListRoutes: MCPTool = { title: "List Routes", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, description: - "Discover all routes in the project. Returns pages, API routes, layouts, and special routes. Use this to understand the project structure before making changes.", + "Use this when you need to discover all routes in the project including pages, API routes, layouts, error, loading, and not-found routes. Returns an array of route info with path, type, and file. Do not use for rendering a route — use vf_preview_route instead.", inputSchema: listRoutesInput, execute: (input) => withSpan( @@ -147,7 +147,7 @@ export const vfGetProjectContext: MCPTool withSpan( @@ -226,7 +226,7 @@ export const vfGetComponentTree: MCPTool withSpan( @@ -384,7 +384,7 @@ export const vfListLocalProjects: MCPTool withSpan( diff --git a/cli/mcp/tools/scaffold-tools.ts b/cli/mcp/tools/scaffold-tools.ts index ec6e027aef..6f88b5a688 100644 --- a/cli/mcp/tools/scaffold-tools.ts +++ b/cli/mcp/tools/scaffold-tools.ts @@ -212,7 +212,7 @@ export const vfScaffold: MCPTool = { title: "Scaffold Code", annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }, description: - "Generate new entities (pages, API routes, layouts, components, AI tools, agents, prompts) with proper conventions. This is the recommended way to create new files in a Veryfront project.", + "Use this when you need to generate new pages, API routes, layouts, components, AI tools, agents, or prompts with proper Veryfront conventions. Returns the created file path and content. May overwrite existing files at the target path. Do not use for creating entire projects — use vf_create_project instead.", inputSchema: scaffoldInput, execute: (input) => withSpan( @@ -405,7 +405,7 @@ export const vfGetConventions: MCPTool = { title: "Get Conventions", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, description: - "Get Veryfront coding conventions and best practices. Use this as guardrails when writing code to ensure consistency with the project standards.", + "Use this when you need Veryfront coding conventions and best practices for routing, API, components, AI, or styling. Do not use for project structure — use vf_get_project_context instead.", inputSchema: getConventionsInput, execute: (input) => { if (input.topic === "all") return Promise.resolve(Object.values(CONVENTIONS)); diff --git a/cli/mcp/tools/skill-tools.ts b/cli/mcp/tools/skill-tools.ts index d82091a744..3883e972eb 100644 --- a/cli/mcp/tools/skill-tools.ts +++ b/cli/mcp/tools/skill-tools.ts @@ -94,7 +94,7 @@ export const vfGetSkills: MCPTool = { title: "Get Skills", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, description: - "Discover available Agent Skills for Veryfront development. Skills provide procedural knowledge for using MCP tools effectively. Call without name param to list all skills, or with name to get full skill content.", + "Use this when you need to discover available Agent Skills or load a specific skill's procedural knowledge. Returns skill names and descriptions, or full skill content when name is provided. Do not use for skill reference docs — use vf_get_skill_reference instead.", inputSchema: getSkillsInput, execute: (input) => withSpan( @@ -173,7 +173,7 @@ export const vfGetSkillReference: MCPTool { const fs = getFs(); diff --git a/src/issues/mcp.ts b/src/issues/mcp.ts index 01b2a1ae35..f50461b35e 100644 --- a/src/issues/mcp.ts +++ b/src/issues/mcp.ts @@ -55,8 +55,9 @@ const issuesCreate: MCPTool = { name: "issues_create", title: "Create Issue", annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }, - description: "Create a new issue, task, or plan as a markdown file. " + - "Use prefix 'TASK' for small work items, 'PLAN' for proposals/RFCs, 'ISSUE' for bugs/features.", + description: "Use this when you need to create a new issue, task, or plan as a markdown file. " + + "Use prefix 'TASK' for small work items, 'PLAN' for proposals/RFCs, 'ISSUE' for bugs/features. " + + "Returns the created issue. Do not use for updating — use issues_update instead.", inputSchema: issuesCreateInput, execute: async (input) => { const manager = getManager(input.projectDir); @@ -85,7 +86,8 @@ const issuesGet: MCPTool = { name: "issues_get", title: "Get Issue", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, - description: "Get a specific issue by ID. Returns null if not found.", + description: + "Use this when you need to retrieve a specific issue by its ID. Returns the issue or null if not found. Do not use for listing — use issues_list instead.", inputSchema: issuesGetInput, execute: async (input) => { const manager = getManager(input.projectDir); @@ -118,8 +120,9 @@ const issuesUpdate: MCPTool = { idempotentHint: true, openWorldHint: false, }, - description: "Update an existing issue. Only provided fields are updated. " + - "Returns the updated issue or null if not found.", + description: + "Use this when you need to modify an existing issue. Only provided fields are updated. " + + "Returns the updated issue or null if not found. Do not use to close — use issues_close instead.", inputSchema: issuesUpdateInput, execute: async (input) => { const manager = getManager(input.projectDir); @@ -166,8 +169,8 @@ const issuesList: MCPTool = { name: "issues_list", title: "List Issues", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, - description: "List issues with filtering and sorting. " + - "Returns matching issues and total count.", + description: "Use this when you need to find issues matching criteria. " + + "Returns matching issues and total count. Do not use to get a single known issue — use issues_get instead.", inputSchema: issuesListInput, execute: async (input) => { const manager = getManager(input.projectDir); @@ -203,7 +206,8 @@ const issuesClose: MCPTool = { idempotentHint: true, openWorldHint: false, }, - description: "Close an issue. Returns the updated issue or null if not found.", + description: + "Use this when you need to close an issue. Returns the updated issue or null if not found. Do not use to delete — use issues_delete instead.", inputSchema: issuesCloseInput, execute: async (input) => { const manager = getManager(input.projectDir); @@ -234,8 +238,10 @@ const issuesDelete: MCPTool = { idempotentHint: true, openWorldHint: false, }, - description: "Permanently delete an issue file. " + - "Use with caution - this cannot be undone.", + description: + "Use this when you need to permanently delete an issue. Returns {deleted: true/false}. " + + "WARNING: this is irreversible and cannot be undone. Prefer issues_close unless permanent deletion is explicitly requested. " + + "Do not use to close — use issues_close instead.", inputSchema: issuesDeleteInput, execute: async (input) => { const manager = getManager(input.projectDir);