Skip to content
48 changes: 32 additions & 16 deletions cli/mcp/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof getErrorsInput>;
Expand All @@ -48,7 +52,7 @@ export const vfGetErrors: MCPTool<GetErrorsInput, DevError[]> = {
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({
Expand All @@ -62,13 +66,21 @@ export const vfGetErrors: MCPTool<GetErrorsInput, DevError[]> = {
};

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<typeof getLogsInput>;
Expand All @@ -78,7 +90,7 @@ export const vfGetLogs: MCPTool<GetLogsInput, LogEntry[]> = {
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({
Expand All @@ -93,7 +105,7 @@ export const vfGetLogs: MCPTool<GetLogsInput, LogEntry[]> = {

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'.",
),
});

Expand All @@ -114,7 +126,7 @@ export const vfClearCache: MCPTool<ClearCacheInput, ClearCacheOutput> = {
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. Clears the specified cache directories. Do not use to fix code errors — those require code changes.",
inputSchema: clearCacheInput,
execute: async (input) => {
const fs = createFileSystem();
Expand Down Expand Up @@ -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();
Expand All @@ -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.",
),
});

Expand All @@ -201,7 +216,8 @@ export const vfClearErrors: MCPTool<ClearErrorsInput, ClearErrorsOutput> = {
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();
Expand Down
10 changes: 5 additions & 5 deletions cli/mcp/tools/catalog-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ export const vfListExamples: MCPTool<ListExamplesInput, ExampleInfo[]> = {
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. Do not use for project templates — use vf_list_templates instead.",
inputSchema: listExamplesInput,
execute: () => Promise.resolve(EXAMPLES),
};
Expand All @@ -355,7 +355,7 @@ export const vfListTemplates: MCPTool<ListTemplatesInput, TemplateInfo[]> = {
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. Do not use for example projects — use vf_list_examples instead.",
inputSchema: listTemplatesInput,
execute: () => Promise.resolve(TEMPLATES),
};
Expand All @@ -379,7 +379,7 @@ export const vfListIntegrations: MCPTool<ListIntegrationsInput, IntegrationInfo[
title: "List Integrations",
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
description:
"List available service integrations (Gmail, Slack, GitHub, etc.). These can be added to AI projects to give agents access to external services.",
"Use this when you need to list available service integrations (Gmail, Slack, GitHub, etc.) that can be added to AI projects. Do not use for adding integrations to a project — use vf_create_project with the integrations parameter instead.",
inputSchema: listIntegrationsInput,
execute: (input) => {
const { category } = input;
Expand All @@ -401,7 +401,7 @@ export const vfListUsecases: MCPTool<ListUsecasesInput, UsecaseInfo[]> = {
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. Do not use for raw templates — use vf_list_templates instead.",
inputSchema: listUsecasesInput,
execute: () => Promise.resolve(USECASES),
};
Expand Down Expand Up @@ -449,7 +449,7 @@ export const vfCreateProject: MCPTool<CreateProjectInput, CreateProjectResult> =
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(
Expand Down
22 changes: 12 additions & 10 deletions cli/mcp/tools/dev-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof hotReloadInput>;
Expand All @@ -38,7 +38,7 @@ export const vfHotReload: MCPTool<HotReloadInput, HotReloadResult> = {
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({
Expand Down Expand Up @@ -80,7 +80,7 @@ export const vfGetDebugContext: MCPTool<GetDebugContextInput, DebugContextResult
title: "Debug Context",
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
description:
"Get the current server context including project info, environment, and mode. Useful for debugging server configuration issues.",
"Use this when you need the dev server's debug context including project slug, environment, request context mode, and multi-project configuration. Do not use for error details — use vf_get_errors instead.",
inputSchema: getDebugContextInput,
execute: (input) =>
withSpan(
Expand Down Expand Up @@ -121,7 +121,7 @@ export const vfGetDebugContext: MCPTool<GetDebugContextInput, DebugContextResult
// ============================================================================

const triggerHmrInput = z.object({
path: z.string().describe("File path that changed (e.g., 'app/page.tsx')"),
path: z.string().describe("File path that changed. Example: 'app/page.tsx'."),
port: z.number().int().min(1).max(65535).optional().default(8080).describe(
"Dev server port (defaults to 8080)",
),
Expand All @@ -144,7 +144,7 @@ export const vfTriggerHmr: MCPTool<TriggerHmrInput, TriggerHmrResult> = {
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.",
inputSchema: triggerHmrInput,
execute: (input) => {
const metrics = ReloadNotifier.getMetrics();
Expand All @@ -170,7 +170,7 @@ export const vfTriggerHmr: MCPTool<TriggerHmrInput, TriggerHmrResult> = {

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)",
Expand All @@ -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<typeof previewRouteInput>;
Expand All @@ -199,7 +201,7 @@ export const vfPreviewRoute: MCPTool<PreviewRouteInput, PreviewRouteResult> = {
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(
Expand Down Expand Up @@ -282,7 +284,7 @@ export const vfWaitForReady: MCPTool<WaitForReadyInput, WaitForReadyResult> = {
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. Do not use to check current status — use vf_get_status instead.",
Comment thread
ariskemper marked this conversation as resolved.
Outdated
inputSchema: waitForReadyInput,
execute: (input) =>
withSpan(
Expand Down Expand Up @@ -372,7 +374,7 @@ export const vfGetFlywheelStatus: MCPTool<GetFlywheelStatusInput, FlywheelStatus
title: "Flywheel Status",
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
description:
"Get aggregated status for the development flywheel. Shows server state, error counts, log summary, and HMR status in one view.",
"Use this when you need a comprehensive status overview combining server health, error counts, and HMR statistics. Do not use for detailed error or log content — use vf_get_errors or vf_get_logs instead.",
inputSchema: getFlywheelStatusInput,
execute: (input) =>
withSpan(
Expand Down
5 changes: 3 additions & 2 deletions cli/mcp/tools/introspection-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. Do not use for project info — use vf_get_project_info instead.",
inputSchema: getSchemaInput,
execute: async (input: { command?: string; category?: string }) => {
if (input.command) {
Expand All @@ -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. Do not use for CLI commands — use vf_get_schema instead.",
inputSchema: getProjectInfoInput,
execute: async () => {
const { getEnvironmentConfig } = await import("veryfront/config");
Expand Down
8 changes: 4 additions & 4 deletions cli/mcp/tools/project-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const vfListRoutes: MCPTool<ListRoutesInput, RouteInfo[]> = {
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. Do not use for rendering a route — use vf_preview_route instead.",
inputSchema: listRoutesInput,
execute: (input) =>
withSpan(
Expand Down Expand Up @@ -147,7 +147,7 @@ export const vfGetProjectContext: MCPTool<GetProjectContextInput, ProjectContext
title: "Project Context",
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
description:
"Get deep understanding of the project structure, conventions, and capabilities. Use this at the start of any coding session to understand the project before making changes.",
"Use this when you need to understand the project structure, conventions, and capabilities at the start of a coding session. Also returns route information. Do not use for detailed per-route rendering — use vf_preview_route instead.",
inputSchema: getProjectContextInput,
execute: (input) =>
withSpan(
Expand Down Expand Up @@ -226,7 +226,7 @@ export const vfGetComponentTree: MCPTool<GetComponentTreeInput, ComponentTreeRes
title: "Component Tree",
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
description:
"Analyze the component hierarchy for a route. Shows layouts, providers, and components that render on this route. Helps understand the rendering structure.",
"Use this when you need to analyze the component hierarchy for a specific route including layouts, providers, and nested components. Do not use for listing all routes — use vf_list_routes instead.",
inputSchema: getComponentTreeInput,
execute: (input) =>
withSpan(
Expand Down Expand Up @@ -384,7 +384,7 @@ export const vfListLocalProjects: MCPTool<ListLocalProjectsInput, LocalProjectIn
title: "List Local Projects",
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
description:
"Discover Veryfront projects on the local filesystem. Scans for veryfront.config.ts files and returns project info including template type and integrations.",
"Use this when you need to discover Veryfront projects on the local filesystem by scanning for veryfront.config.ts files. Do not use for project structure details — use vf_get_project_context instead.",
inputSchema: listLocalProjectsInput,
execute: (input) =>
withSpan(
Expand Down
4 changes: 2 additions & 2 deletions cli/mcp/tools/scaffold-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export const vfScaffold: MCPTool<ScaffoldInput, ScaffoldResult> = {
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. 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(
Expand Down Expand Up @@ -405,7 +405,7 @@ export const vfGetConventions: MCPTool<GetConventionsInput, Convention[]> = {
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));
Expand Down
Loading
Loading