veryfront 0.1.141 → 0.1.142

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/esm/cli/mcp/jsonrpc.d.ts +33 -1
  2. package/esm/cli/mcp/jsonrpc.d.ts.map +1 -1
  3. package/esm/cli/mcp/jsonrpc.js +63 -4
  4. package/esm/cli/mcp/remote-file-tools.d.ts.map +1 -1
  5. package/esm/cli/mcp/remote-file-tools.js +39 -0
  6. package/esm/cli/mcp/server.d.ts.map +1 -1
  7. package/esm/cli/mcp/server.js +57 -34
  8. package/esm/cli/mcp/standalone.d.ts.map +1 -1
  9. package/esm/cli/mcp/standalone.js +24 -11
  10. package/esm/cli/mcp/tools/catalog-tools.d.ts.map +1 -1
  11. package/esm/cli/mcp/tools/catalog-tools.js +15 -5
  12. package/esm/cli/mcp/tools/cicd-tools.d.ts.map +1 -1
  13. package/esm/cli/mcp/tools/cicd-tools.js +8 -0
  14. package/esm/cli/mcp/tools/dev-tools.d.ts.map +1 -1
  15. package/esm/cli/mcp/tools/dev-tools.js +32 -10
  16. package/esm/cli/mcp/tools/introspection-tools.d.ts.map +1 -1
  17. package/esm/cli/mcp/tools/introspection-tools.js +6 -2
  18. package/esm/cli/mcp/tools/project-tools.d.ts.map +1 -1
  19. package/esm/cli/mcp/tools/project-tools.js +12 -4
  20. package/esm/cli/mcp/tools/scaffold-tools.d.ts.map +1 -1
  21. package/esm/cli/mcp/tools/scaffold-tools.js +6 -2
  22. package/esm/cli/mcp/tools/skill-tools.d.ts.map +1 -1
  23. package/esm/cli/mcp/tools/skill-tools.js +6 -2
  24. package/esm/cli/mcp/tools.d.ts.map +1 -1
  25. package/esm/cli/mcp/tools.js +36 -16
  26. package/esm/deno.js +1 -1
  27. package/esm/src/agent/runtime/index.js +1 -1
  28. package/esm/src/agent/runtime/tool-helpers.d.ts.map +1 -1
  29. package/esm/src/agent/runtime/tool-helpers.js +59 -30
  30. package/esm/src/agent/schemas/agent.schema.d.ts +4 -4
  31. package/esm/src/channels/invoke.d.ts +4 -4
  32. package/esm/src/issues/mcp.d.ts.map +1 -1
  33. package/esm/src/issues/mcp.js +39 -10
  34. package/esm/src/mcp/index.d.ts +1 -1
  35. package/esm/src/mcp/index.d.ts.map +1 -1
  36. package/esm/src/mcp/server.d.ts.map +1 -1
  37. package/esm/src/mcp/server.js +85 -25
  38. package/esm/src/mcp/types.d.ts +22 -0
  39. package/esm/src/mcp/types.d.ts.map +1 -1
  40. package/esm/src/tool/types.d.ts +5 -0
  41. package/esm/src/tool/types.d.ts.map +1 -1
  42. package/esm/src/utils/version-constant.d.ts +1 -1
  43. package/esm/src/utils/version-constant.js +1 -1
  44. package/esm/src/workflow/schemas/workflow.schema.d.ts +6 -6
  45. package/package.json +1 -1
  46. package/src/cli/mcp/jsonrpc.ts +72 -4
  47. package/src/cli/mcp/remote-file-tools.ts +39 -0
  48. package/src/cli/mcp/server.ts +66 -33
  49. package/src/cli/mcp/standalone.ts +28 -10
  50. package/src/cli/mcp/tools/catalog-tools.ts +15 -5
  51. package/src/cli/mcp/tools/cicd-tools.ts +8 -0
  52. package/src/cli/mcp/tools/dev-tools.ts +34 -10
  53. package/src/cli/mcp/tools/introspection-tools.ts +7 -2
  54. package/src/cli/mcp/tools/project-tools.ts +12 -4
  55. package/src/cli/mcp/tools/scaffold-tools.ts +6 -2
  56. package/src/cli/mcp/tools/skill-tools.ts +6 -2
  57. package/src/cli/mcp/tools.ts +52 -16
  58. package/src/deno.js +1 -1
  59. package/src/src/agent/runtime/index.ts +1 -1
  60. package/src/src/agent/runtime/tool-helpers.ts +86 -36
  61. package/src/src/issues/mcp.ts +43 -10
  62. package/src/src/mcp/index.ts +7 -1
  63. package/src/src/mcp/server.ts +92 -31
  64. package/src/src/mcp/types.ts +24 -0
  65. package/src/src/tool/types.ts +7 -0
  66. package/src/src/utils/version-constant.ts +1 -1
@@ -13,11 +13,14 @@ import { createHttpServer, type HttpServer } from "../../src/platform/compat/htt
13
13
  import type { StdinReader } from "../../src/platform/index.js";
14
14
  import { withSpan } from "../../src/observability/tracing/otlp-setup.js";
15
15
  import { createIssuesManager } from "../../src/issues/index.js";
16
+ import type { ToolListEntry } from "../../src/mcp/index.js";
16
17
  import { getErrorCollector, getLogBuffer } from "../../src/observability/index.js";
17
18
  import { allTools, getTool, setServerStartTime } from "./tools.js";
18
19
  import { startStdioJsonRpc } from "./stdio.js";
19
20
  import {
21
+ buildInitializeResult,
20
22
  errorResponse,
23
+ JsonRpcError,
21
24
  type JSONRPCRequest,
22
25
  JSONRPCRequestSchema,
23
26
  type JSONRPCResponse,
@@ -121,6 +124,17 @@ export class MCPDevServer {
121
124
 
122
125
  if (req.method === "OPTIONS") return new dntShim.Response(null, { status: 204, headers });
123
126
 
127
+ if (origin && !isAllowedOrigin) {
128
+ return new dntShim.Response(
129
+ JSON.stringify({
130
+ jsonrpc: "2.0",
131
+ id: null,
132
+ error: { code: -32600, message: "Forbidden: Origin not allowed" },
133
+ }),
134
+ { status: 403, headers },
135
+ );
136
+ }
137
+
124
138
  if (url.pathname !== "/mcp") {
125
139
  return new dntShim.Response(JSON.stringify({ error: "Not found. MCP endpoint is at /mcp" }), {
126
140
  status: 404,
@@ -168,6 +182,8 @@ export class MCPDevServer {
168
182
  switch (method) {
169
183
  case "initialize":
170
184
  return Promise.resolve(this.handleInitialize(params));
185
+ case "notifications/initialized":
186
+ return Promise.resolve({});
171
187
  case "tools/list":
172
188
  return Promise.resolve(this.handleToolsList());
173
189
  case "tools/call":
@@ -185,53 +201,70 @@ export class MCPDevServer {
185
201
  }
186
202
  }
187
203
 
188
- private handleInitialize(_params: unknown): unknown {
189
- return {
190
- protocolVersion: "2024-11-05",
191
- capabilities: {
192
- tools: {},
193
- resources: {},
194
- prompts: {},
204
+ private handleInitialize(params: unknown): unknown {
205
+ return buildInitializeResult(
206
+ params,
207
+ {
208
+ name: this.config.serverName ?? "veryfront-dev",
209
+ title: "Veryfront Dev MCP Server",
210
+ version: this.config.serverVersion ?? "1.0.0",
211
+ description:
212
+ "Veryfront development server tools for real-time errors, logs, HMR, and scaffolding",
195
213
  },
196
- serverInfo: {
197
- name: this.config.serverName,
198
- version: this.config.serverVersion,
199
- },
200
- };
214
+ "Veryfront dev MCP server provides development tools. Use vf_get_errors to check for code errors, vf_get_logs for server logs, and vf_trigger_hmr for hot module reload.",
215
+ );
201
216
  }
202
217
 
203
- private handleToolsList(): unknown {
218
+ private handleToolsList(): { tools: ToolListEntry[] } {
204
219
  return {
205
- tools: allTools.map((tool) => ({
206
- name: tool.name,
207
- description: tool.description,
208
- inputSchema: this.zodToJsonSchema(tool.inputSchema),
209
- })),
220
+ tools: allTools.map((tool) => {
221
+ const entry: ToolListEntry = {
222
+ name: tool.name,
223
+ description: tool.description,
224
+ inputSchema: this.zodToJsonSchema(tool.inputSchema),
225
+ };
226
+ if (tool.title) entry.title = tool.title;
227
+ if (tool.annotations) entry.annotations = tool.annotations;
228
+ return entry;
229
+ }),
210
230
  };
211
231
  }
212
232
 
213
233
  private handleToolsCall(params: unknown): Promise<unknown> {
214
- const { name, arguments: args } = ToolsCallParamsSchema.parse(params);
234
+ const { name: toolName, arguments: args } = ToolsCallParamsSchema.parse(params);
235
+
236
+ const tool = getTool(toolName);
237
+ if (!tool) {
238
+ throw new JsonRpcError(-32602, `Unknown tool: ${toolName}`);
239
+ }
240
+
241
+ let input: unknown;
242
+ try {
243
+ input = tool.inputSchema.parse(args ?? {});
244
+ } catch (error) {
245
+ const message = error instanceof Error ? error.message : String(error);
246
+ throw new JsonRpcError(-32602, `Invalid arguments for tool ${toolName}: ${message}`);
247
+ }
215
248
 
216
249
  return withSpan(
217
250
  "cli.mcp.handleToolsCall",
218
251
  async () => {
219
- const tool = getTool(name);
220
- if (!tool) throw new Error(`Unknown tool: ${name}`);
221
-
222
- const input = tool.inputSchema.parse(args ?? {});
223
- const result = await tool.execute(input);
252
+ try {
253
+ const result = await tool.execute(input);
224
254
 
225
- return {
226
- content: [
227
- {
228
- type: "text",
229
- text: JSON.stringify(result, null, 2),
230
- },
231
- ],
232
- };
255
+ return {
256
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
257
+ isError: false,
258
+ };
259
+ } catch (error) {
260
+ const message = error instanceof Error ? error.message : String(error);
261
+ return {
262
+ content: [{ type: "text", text: message }],
263
+ isError: true,
264
+ };
265
+ }
233
266
  },
234
- { "mcp.tool.name": name },
267
+ { "mcp.tool.name": toolName },
235
268
  );
236
269
  }
237
270
 
@@ -11,7 +11,9 @@ import type { StdinReader } from "../../src/platform/index.js";
11
11
  import { DevServerClient } from "./dev-server-client.js";
12
12
  import { startStdioJsonRpc } from "./stdio.js";
13
13
  import {
14
+ buildInitializeResult,
14
15
  errorResponse,
16
+ JsonRpcError,
15
17
  type JSONRPCRequest,
16
18
  JSONRPCRequestSchema,
17
19
  type JSONRPCResponse,
@@ -79,11 +81,16 @@ export class StandaloneMCPServer {
79
81
  private dispatchMethod(method: string, params: unknown): Promise<unknown> {
80
82
  switch (method) {
81
83
  case "initialize":
82
- return Promise.resolve({
83
- protocolVersion: "2024-11-05",
84
- capabilities: { tools: {}, resources: {}, prompts: {} },
85
- serverInfo: { name: "veryfront-mcp", version: "1.0.0" },
86
- });
84
+ return Promise.resolve(buildInitializeResult(
85
+ params,
86
+ {
87
+ name: "veryfront-mcp",
88
+ title: "Veryfront Standalone MCP Server",
89
+ version: "1.0.0",
90
+ description: "Veryfront standalone MCP server for CLI-based development tools",
91
+ },
92
+ "Veryfront standalone MCP server provides development tools. Use vf_get_errors to check for code errors, vf_get_logs for server logs, and vf_get_status for dev server health.",
93
+ ));
87
94
  case "notifications/initialized":
88
95
  return Promise.resolve({});
89
96
  case "tools/list":
@@ -110,13 +117,24 @@ export class StandaloneMCPServer {
110
117
  }
111
118
 
112
119
  private async handleToolsCall(params: unknown): Promise<unknown> {
113
- const { name, arguments: args } = ToolsCallParamsSchema.parse(params);
120
+ const { name: toolName, arguments: args } = ToolsCallParamsSchema.parse(params);
114
121
 
115
- const tool = this.tools.find((t) => t.name === name);
116
- if (!tool) throw new Error(`Unknown tool: ${name}`);
122
+ const tool = this.tools.find((t) => t.name === toolName);
123
+ if (!tool) throw new JsonRpcError(-32602, `Unknown tool: ${toolName}`);
117
124
 
118
- const result = await tool.execute(args ?? {});
119
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
125
+ try {
126
+ const result = await tool.execute(args ?? {});
127
+ return {
128
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
129
+ isError: false,
130
+ };
131
+ } catch (error) {
132
+ const message = error instanceof Error ? error.message : String(error);
133
+ return {
134
+ content: [{ type: "text", text: message }],
135
+ isError: true,
136
+ };
137
+ }
120
138
  }
121
139
 
122
140
  private handleResourcesList(): unknown {
@@ -334,8 +334,10 @@ type ListExamplesInput = z.infer<typeof listExamplesInput>;
334
334
 
335
335
  export const vfListExamples: MCPTool<ListExamplesInput, ExampleInfo[]> = {
336
336
  name: "vf_list_examples",
337
+ title: "List Examples",
338
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
337
339
  description:
338
- "List example projects that demonstrate Veryfront features. Use these as references or starting points for new projects.",
340
+ "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.",
339
341
  inputSchema: listExamplesInput,
340
342
  execute: () => Promise.resolve(EXAMPLES),
341
343
  };
@@ -350,8 +352,10 @@ type ListTemplatesInput = z.infer<typeof listTemplatesInput>;
350
352
 
351
353
  export const vfListTemplates: MCPTool<ListTemplatesInput, TemplateInfo[]> = {
352
354
  name: "vf_list_templates",
355
+ title: "List Templates",
356
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }, // openWorldHint: templates come from remote catalog API
353
357
  description:
354
- "List available project templates. Use this to help users choose the right starting point for their project.",
358
+ "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.",
355
359
  inputSchema: listTemplatesInput,
356
360
  execute: () => Promise.resolve(TEMPLATES),
357
361
  };
@@ -372,8 +376,10 @@ type ListIntegrationsInput = z.infer<typeof listIntegrationsInput>;
372
376
 
373
377
  export const vfListIntegrations: MCPTool<ListIntegrationsInput, IntegrationInfo[]> = {
374
378
  name: "vf_list_integrations",
379
+ title: "List Integrations",
380
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
375
381
  description:
376
- "List available service integrations (Gmail, Slack, GitHub, etc.). These can be added to AI projects to give agents access to external services.",
382
+ "Use this when you need to list available service integrations (Gmail, Slack, GitHub, etc.) that can be added to AI projects. Returns an array of integration info with name, category, and description. Do not use for adding integrations to a project — use vf_create_project with the integrations parameter instead.",
377
383
  inputSchema: listIntegrationsInput,
378
384
  execute: (input) => {
379
385
  const { category } = input;
@@ -392,8 +398,10 @@ type ListUsecasesInput = z.infer<typeof listUsecasesInput>;
392
398
 
393
399
  export const vfListUsecases: MCPTool<ListUsecasesInput, UsecaseInfo[]> = {
394
400
  name: "vf_list_usecases",
401
+ title: "List Use Cases",
402
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
395
403
  description:
396
- "List pre-configured use-case templates. Each includes recommended integrations and UI layout for common scenarios.",
404
+ "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.",
397
405
  inputSchema: listUsecasesInput,
398
406
  execute: () => Promise.resolve(USECASES),
399
407
  };
@@ -438,8 +446,10 @@ interface CreateProjectResult {
438
446
 
439
447
  export const vfCreateProject: MCPTool<CreateProjectInput, CreateProjectResult> = {
440
448
  name: "vf_create_project",
449
+ title: "Create Project",
450
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
441
451
  description:
442
- "Create a new Veryfront project from a template. This is the MCP equivalent of 'veryfront init'. Returns the project directory and next steps.",
452
+ "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.",
443
453
  inputSchema: createProjectInput,
444
454
  execute: (input) =>
445
455
  withSpan(
@@ -8,6 +8,8 @@ const getPipelineStatusInput = z.object({
8
8
 
9
9
  const vfGetPipelineStatus: MCPTool = {
10
10
  name: "vf_get_pipeline_status",
11
+ title: "Pipeline Status",
12
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
11
13
  description: "Get the current build/deploy pipeline state for a project environment.",
12
14
  inputSchema: getPipelineStatusInput,
13
15
  execute: async (input: { projectSlug: string; environment: string }) => {
@@ -27,6 +29,8 @@ const getDeployHistoryInput = z.object({
27
29
 
28
30
  const vfGetDeployHistory: MCPTool = {
29
31
  name: "vf_get_deploy_history",
32
+ title: "Deploy History",
33
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
30
34
  description: "List recent deployments with status, version, URL, and timestamp.",
31
35
  inputSchema: getDeployHistoryInput,
32
36
  execute: async (input: { projectSlug: string; limit: number }) => {
@@ -45,6 +49,8 @@ const getBuildLogsInput = z.object({
45
49
 
46
50
  const vfGetBuildLogs: MCPTool = {
47
51
  name: "vf_get_build_logs",
52
+ title: "Build Logs",
53
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
48
54
  description: "Get build logs from an active or recent build/deployment.",
49
55
  inputSchema: getBuildLogsInput,
50
56
  execute: async (input: { projectSlug: string; deployId?: string }) => {
@@ -64,6 +70,8 @@ const triggerDeployInput = z.object({
64
70
 
65
71
  const vfTriggerDeploy: MCPTool = {
66
72
  name: "vf_trigger_deploy",
73
+ title: "Trigger Deploy",
74
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
67
75
  description:
68
76
  "Trigger a deployment to an environment. Returns a deployment ID for status tracking.",
69
77
  inputSchema: triggerDeployInput,
@@ -20,7 +20,7 @@ const hotReloadInput = z.object({
20
20
  file: z
21
21
  .string()
22
22
  .optional()
23
- .describe("Specific file to trigger reload for (optional - reloads all if not specified)"),
23
+ .describe("Specific file to trigger reload for. Example: 'app/page.tsx'. Omit to reload all."),
24
24
  });
25
25
 
26
26
  type HotReloadInput = z.infer<typeof hotReloadInput>;
@@ -32,8 +32,15 @@ interface HotReloadResult {
32
32
 
33
33
  export const vfHotReload: MCPTool<HotReloadInput, HotReloadResult> = {
34
34
  name: "vf_hot_reload",
35
+ title: "Hot Reload",
36
+ annotations: {
37
+ readOnlyHint: false,
38
+ destructiveHint: false,
39
+ idempotentHint: true,
40
+ openWorldHint: false,
41
+ },
35
42
  description:
36
- "Trigger a hot reload of the dev server. Use after making changes to see them instantly.",
43
+ "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.",
37
44
  inputSchema: hotReloadInput,
38
45
  execute: () =>
39
46
  Promise.resolve({
@@ -72,8 +79,10 @@ interface DebugContextResult {
72
79
 
73
80
  export const vfGetDebugContext: MCPTool<GetDebugContextInput, DebugContextResult> = {
74
81
  name: "vf_get_debug_context",
82
+ title: "Debug Context",
83
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
75
84
  description:
76
- "Get the current server context including project info, environment, and mode. Useful for debugging server configuration issues.",
85
+ "Use this when you need the dev server's debug context including project slug, environment, request context mode, and multi-project configuration. Returns project info and server mode. Do not use for error details use vf_get_errors instead.",
77
86
  inputSchema: getDebugContextInput,
78
87
  execute: (input) =>
79
88
  withSpan(
@@ -114,7 +123,7 @@ export const vfGetDebugContext: MCPTool<GetDebugContextInput, DebugContextResult
114
123
  // ============================================================================
115
124
 
116
125
  const triggerHmrInput = z.object({
117
- path: z.string().describe("File path that changed (e.g., 'app/page.tsx')"),
126
+ path: z.string().describe("File path that changed. Example: 'app/page.tsx'."),
118
127
  port: z.number().int().min(1).max(65535).optional().default(8080).describe(
119
128
  "Dev server port (defaults to 8080)",
120
129
  ),
@@ -129,8 +138,15 @@ interface TriggerHmrResult {
129
138
 
130
139
  export const vfTriggerHmr: MCPTool<TriggerHmrInput, TriggerHmrResult> = {
131
140
  name: "vf_trigger_hmr",
141
+ title: "Trigger HMR",
142
+ annotations: {
143
+ readOnlyHint: false,
144
+ destructiveHint: false,
145
+ idempotentHint: true,
146
+ openWorldHint: false,
147
+ },
132
148
  description:
133
- "Trigger Hot Module Replacement for a specific file. The browser will update without a full reload.",
149
+ "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.",
134
150
  inputSchema: triggerHmrInput,
135
151
  execute: (input) => {
136
152
  const metrics = ReloadNotifier.getMetrics();
@@ -156,7 +172,7 @@ export const vfTriggerHmr: MCPTool<TriggerHmrInput, TriggerHmrResult> = {
156
172
 
157
173
  const previewRouteInput = z.object({
158
174
  route: z.string().startsWith("/", "Route must start with /").describe(
159
- "Route path to preview (e.g., '/', '/dashboard', '/api/users')",
175
+ "Route path to preview. Example: '/', '/dashboard', '/api/users'.",
160
176
  ),
161
177
  port: z.number().int().min(1).max(65535).optional().default(8080).describe(
162
178
  "Dev server port (defaults to 8080)",
@@ -165,7 +181,9 @@ const previewRouteInput = z.object({
165
181
  .enum(["html", "json", "status"])
166
182
  .optional()
167
183
  .default("status")
168
- .describe("Output format: html (full page), json (API response), status (just HTTP status)"),
184
+ .describe(
185
+ "Output format: 'html' for full page, 'json' for API response, 'status' for just HTTP status. Defaults to 'status'.",
186
+ ),
169
187
  });
170
188
 
171
189
  type PreviewRouteInput = z.infer<typeof previewRouteInput>;
@@ -182,8 +200,10 @@ interface PreviewRouteResult {
182
200
 
183
201
  export const vfPreviewRoute: MCPTool<PreviewRouteInput, PreviewRouteResult> = {
184
202
  name: "vf_preview_route",
203
+ title: "Preview Route",
204
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
185
205
  description:
186
- "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.",
206
+ "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.",
187
207
  inputSchema: previewRouteInput,
188
208
  execute: (input) =>
189
209
  withSpan(
@@ -263,8 +283,10 @@ interface WaitForReadyResult {
263
283
 
264
284
  export const vfWaitForReady: MCPTool<WaitForReadyInput, WaitForReadyResult> = {
265
285
  name: "vf_wait_for_ready",
286
+ title: "Wait for Ready",
287
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
266
288
  description:
267
- "Wait for the server to be ready by polling the health endpoint. Use this after starting the server to ensure it's accepting requests.",
289
+ "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.",
268
290
  inputSchema: waitForReadyInput,
269
291
  execute: (input) =>
270
292
  withSpan(
@@ -351,8 +373,10 @@ interface FlywheelStatus {
351
373
 
352
374
  export const vfGetFlywheelStatus: MCPTool<GetFlywheelStatusInput, FlywheelStatus> = {
353
375
  name: "vf_get_flywheel_status",
376
+ title: "Flywheel Status",
377
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
354
378
  description:
355
- "Get aggregated status for the development flywheel. Shows server state, error counts, log summary, and HMR status in one view.",
379
+ "Use this when you need a comprehensive status overview combining server health, error counts, and HMR statistics. Returns server status, error/log counts, and HMR metrics in one response. Do not use for detailed error or log content — use vf_get_errors or vf_get_logs instead.",
356
380
  inputSchema: getFlywheelStatusInput,
357
381
  execute: (input) =>
358
382
  withSpan(
@@ -11,8 +11,10 @@ const getSchemaInput = z.object({
11
11
 
12
12
  const vfGetSchema: MCPTool = {
13
13
  name: "vf_get_schema",
14
+ title: "Get CLI Schema",
15
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
14
16
  description:
15
- "Get the CLI command schema for discovering available commands, arguments, and flags.",
17
+ "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.",
16
18
  inputSchema: getSchemaInput,
17
19
  execute: async (input: { command?: string; category?: string }) => {
18
20
  if (input.command) {
@@ -26,7 +28,10 @@ const getProjectInfoInput = z.object({});
26
28
 
27
29
  const vfGetProjectInfo: MCPTool = {
28
30
  name: "vf_get_project_info",
29
- description: "Get project metadata including project slug, version, and environment.",
31
+ title: "Get Project Info",
32
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
33
+ description:
34
+ "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.",
30
35
  inputSchema: getProjectInfoInput,
31
36
  execute: async () => {
32
37
  const { getEnvironmentConfig } = await import("../../../src/config/index.js");
@@ -37,8 +37,10 @@ type ListRoutesInput = z.infer<typeof listRoutesInput>;
37
37
 
38
38
  export const vfListRoutes: MCPTool<ListRoutesInput, RouteInfo[]> = {
39
39
  name: "vf_list_routes",
40
+ title: "List Routes",
41
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
40
42
  description:
41
- "Discover all routes in the project. Returns pages, API routes, layouts, and special routes. Use this to understand the project structure before making changes.",
43
+ "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.",
42
44
  inputSchema: listRoutesInput,
43
45
  execute: (input) =>
44
46
  withSpan(
@@ -142,8 +144,10 @@ async function getProjectName(projectDir: string, fs: FileSystem): Promise<strin
142
144
 
143
145
  export const vfGetProjectContext: MCPTool<GetProjectContextInput, ProjectContext> = {
144
146
  name: "vf_get_project_context",
147
+ title: "Project Context",
148
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
145
149
  description:
146
- "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.",
150
+ "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.",
147
151
  inputSchema: getProjectContextInput,
148
152
  execute: (input) =>
149
153
  withSpan(
@@ -219,8 +223,10 @@ function toRelativePath(absolutePath: string, projectDir: string): string {
219
223
 
220
224
  export const vfGetComponentTree: MCPTool<GetComponentTreeInput, ComponentTreeResult> = {
221
225
  name: "vf_get_component_tree",
226
+ title: "Component Tree",
227
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
222
228
  description:
223
- "Analyze the component hierarchy for a route. Shows layouts, providers, and components that render on this route. Helps understand the rendering structure.",
229
+ "Use this when you need to analyze the component hierarchy for a specific route including layouts, providers, and nested components. Returns the component tree structure. Do not use for listing all routes — use vf_list_routes instead.",
224
230
  inputSchema: getComponentTreeInput,
225
231
  execute: (input) =>
226
232
  withSpan(
@@ -375,8 +381,10 @@ async function scanForProjects(
375
381
 
376
382
  export const vfListLocalProjects: MCPTool<ListLocalProjectsInput, LocalProjectInfo[]> = {
377
383
  name: "vf_list_local_projects",
384
+ title: "List Local Projects",
385
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
378
386
  description:
379
- "Discover Veryfront projects on the local filesystem. Scans for veryfront.config.ts files and returns project info including template type and integrations.",
387
+ "Use this when you need to discover Veryfront projects on the local filesystem by scanning for veryfront.config.ts files. Returns project info including template type and integrations. Do not use for project structure details — use vf_get_project_context instead.",
380
388
  inputSchema: listLocalProjectsInput,
381
389
  execute: (input) =>
382
390
  withSpan(
@@ -209,8 +209,10 @@ const SCAFFOLD_CONFIGS: Record<ScaffoldType, ScaffoldConfig> = {
209
209
 
210
210
  export const vfScaffold: MCPTool<ScaffoldInput, ScaffoldResult> = {
211
211
  name: "vf_scaffold",
212
+ title: "Scaffold Code",
213
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
212
214
  description:
213
- "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.",
215
+ "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.",
214
216
  inputSchema: scaffoldInput,
215
217
  execute: (input) =>
216
218
  withSpan(
@@ -400,8 +402,10 @@ const CONVENTIONS: Record<string, Convention> = {
400
402
 
401
403
  export const vfGetConventions: MCPTool<GetConventionsInput, Convention[]> = {
402
404
  name: "vf_get_conventions",
405
+ title: "Get Conventions",
406
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
403
407
  description:
404
- "Get Veryfront coding conventions and best practices. Use this as guardrails when writing code to ensure consistency with the project standards.",
408
+ "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.",
405
409
  inputSchema: getConventionsInput,
406
410
  execute: (input) => {
407
411
  if (input.topic === "all") return Promise.resolve(Object.values(CONVENTIONS));
@@ -91,8 +91,10 @@ interface GetSkillsResult {
91
91
 
92
92
  export const vfGetSkills: MCPTool<GetSkillsInput, GetSkillsResult> = {
93
93
  name: "vf_get_skills",
94
+ title: "Get Skills",
95
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
94
96
  description:
95
- "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.",
97
+ "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.",
96
98
  inputSchema: getSkillsInput,
97
99
  execute: (input) =>
98
100
  withSpan(
@@ -168,8 +170,10 @@ interface GetSkillReferenceResult {
168
170
 
169
171
  export const vfGetSkillReference: MCPTool<GetSkillReferenceInput, GetSkillReferenceResult> = {
170
172
  name: "vf_get_skill_reference",
173
+ title: "Get Skill Reference",
174
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
171
175
  description:
172
- "Get a specific reference document from a skill. Use this to load detailed documentation on demand.",
176
+ "Use this when you need to load a specific reference document from a skill. Returns the document content as text. Do not use for skill discovery — use vf_get_skills instead.",
173
177
  inputSchema: getSkillReferenceInput,
174
178
  execute: async (input) => {
175
179
  const fs = getFs();