veryfront 0.1.140 → 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 (72) 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/internal-agents/run-stream.d.ts.map +1 -1
  33. package/esm/src/internal-agents/run-stream.js +62 -0
  34. package/esm/src/issues/mcp.d.ts.map +1 -1
  35. package/esm/src/issues/mcp.js +39 -10
  36. package/esm/src/mcp/index.d.ts +1 -1
  37. package/esm/src/mcp/index.d.ts.map +1 -1
  38. package/esm/src/mcp/server.d.ts.map +1 -1
  39. package/esm/src/mcp/server.js +85 -25
  40. package/esm/src/mcp/types.d.ts +22 -0
  41. package/esm/src/mcp/types.d.ts.map +1 -1
  42. package/esm/src/server/handlers/request/agent-stream.handler.d.ts.map +1 -1
  43. package/esm/src/server/handlers/request/agent-stream.handler.js +30 -0
  44. package/esm/src/tool/types.d.ts +5 -0
  45. package/esm/src/tool/types.d.ts.map +1 -1
  46. package/esm/src/utils/version-constant.d.ts +1 -1
  47. package/esm/src/utils/version-constant.js +1 -1
  48. package/esm/src/workflow/schemas/workflow.schema.d.ts +6 -6
  49. package/package.json +1 -1
  50. package/src/cli/mcp/jsonrpc.ts +72 -4
  51. package/src/cli/mcp/remote-file-tools.ts +39 -0
  52. package/src/cli/mcp/server.ts +66 -33
  53. package/src/cli/mcp/standalone.ts +28 -10
  54. package/src/cli/mcp/tools/catalog-tools.ts +15 -5
  55. package/src/cli/mcp/tools/cicd-tools.ts +8 -0
  56. package/src/cli/mcp/tools/dev-tools.ts +34 -10
  57. package/src/cli/mcp/tools/introspection-tools.ts +7 -2
  58. package/src/cli/mcp/tools/project-tools.ts +12 -4
  59. package/src/cli/mcp/tools/scaffold-tools.ts +6 -2
  60. package/src/cli/mcp/tools/skill-tools.ts +6 -2
  61. package/src/cli/mcp/tools.ts +52 -16
  62. package/src/deno.js +1 -1
  63. package/src/src/agent/runtime/index.ts +1 -1
  64. package/src/src/agent/runtime/tool-helpers.ts +86 -36
  65. package/src/src/internal-agents/run-stream.ts +62 -0
  66. package/src/src/issues/mcp.ts +43 -10
  67. package/src/src/mcp/index.ts +7 -1
  68. package/src/src/mcp/server.ts +92 -31
  69. package/src/src/mcp/types.ts +24 -0
  70. package/src/src/server/handlers/request/agent-stream.handler.ts +30 -0
  71. package/src/src/tool/types.ts +7 -0
  72. package/src/src/utils/version-constant.ts +1 -1
@@ -43,6 +43,18 @@ export const JSONRPC_ERRORS = {
43
43
  INTERNAL_ERROR: -32603,
44
44
  } as const;
45
45
 
46
+ /**
47
+ * Error with a JSON-RPC error code attached.
48
+ * Preserves stack traces unlike throwing plain objects.
49
+ */
50
+ export class JsonRpcError extends Error {
51
+ readonly code: number;
52
+ constructor(code: number, message: string) {
53
+ super(message);
54
+ this.code = code;
55
+ }
56
+ }
57
+
46
58
  /**
47
59
  * Create a JSON-RPC parse error response
48
60
  */
@@ -70,15 +82,71 @@ export function successResponse(id: string | number | undefined, result: unknown
70
82
  export function errorResponse(
71
83
  id: string | number | undefined,
72
84
  e: unknown,
73
- code = JSONRPC_ERRORS.INTERNAL_ERROR,
85
+ code?: number,
74
86
  ): JSONRPCResponse {
87
+ const errorCode = typeof e === "object" && e !== null && "code" in e
88
+ ? (e as { code: unknown }).code
89
+ : undefined;
90
+ const resolvedCode = code ??
91
+ (typeof errorCode === "number" ? errorCode : JSONRPC_ERRORS.INTERNAL_ERROR);
92
+ const message = e instanceof Error
93
+ ? e.message
94
+ : typeof e === "object" && e !== null && "message" in e
95
+ ? String((e as { message: unknown }).message)
96
+ : String(e);
75
97
  return {
76
98
  jsonrpc: "2.0",
77
99
  id,
78
- error: {
79
- code,
80
- message: e instanceof Error ? e.message : String(e),
100
+ error: { code: resolvedCode, message },
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Supported MCP protocol versions (newest first).
106
+ * Shared across all CLI MCP servers so the version list is maintained in one place.
107
+ */
108
+ export const MCP_SUPPORTED_VERSIONS: [string, ...string[]] = ["2025-11-25", "2024-11-05"];
109
+
110
+ /**
111
+ * Safely extract a record from unknown params (mirrors src/mcp toParamsRecord).
112
+ */
113
+ export function toParamsRecord(params: unknown): Record<string, unknown> {
114
+ if (params && typeof params === "object" && !Array.isArray(params)) {
115
+ return params as Record<string, unknown>;
116
+ }
117
+ return {};
118
+ }
119
+
120
+ /**
121
+ * Negotiate MCP protocol version: echo the client's version if supported,
122
+ * otherwise fall back to the newest supported version.
123
+ */
124
+ export function negotiateVersion(params: unknown): string {
125
+ const p = toParamsRecord(params);
126
+ const requested = typeof p.protocolVersion === "string" ? p.protocolVersion : undefined;
127
+ return requested && MCP_SUPPORTED_VERSIONS.includes(requested)
128
+ ? requested
129
+ : MCP_SUPPORTED_VERSIONS[0];
130
+ }
131
+
132
+ /**
133
+ * Build a complete MCP initialize result with negotiated version,
134
+ * capabilities, serverInfo, and instructions.
135
+ */
136
+ export function buildInitializeResult(
137
+ params: unknown,
138
+ serverInfo: { name: string; title: string; version: string; description: string },
139
+ instructions: string,
140
+ ): Record<string, unknown> {
141
+ return {
142
+ protocolVersion: negotiateVersion(params),
143
+ capabilities: {
144
+ tools: { listChanged: true },
145
+ resources: { listChanged: true },
146
+ prompts: { listChanged: true },
81
147
  },
148
+ serverInfo,
149
+ instructions,
82
150
  };
83
151
  }
84
152
 
@@ -200,6 +200,8 @@ interface RemoteListFilesOutput {
200
200
 
201
201
  export const vfRemoteListFiles: MCPTool<RemoteListFilesInput, RemoteListFilesOutput> = {
202
202
  name: "vf_remote_list_files",
203
+ title: "List Remote Files",
204
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
203
205
  description:
204
206
  "List files in a remote Veryfront project. Returns file paths, types, and sizes. Use this to explore a project's structure.",
205
207
  inputSchema: remoteListFilesInput,
@@ -249,6 +251,8 @@ interface RemoteGetFileOutput {
249
251
 
250
252
  export const vfRemoteGetFile: MCPTool<RemoteGetFileInput, RemoteGetFileOutput> = {
251
253
  name: "vf_remote_get_file",
254
+ title: "Get Remote File",
255
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
252
256
  description:
253
257
  "Read the content of a file from a remote Veryfront project. Always use this before modifying a file.",
254
258
  inputSchema: remoteGetFileInput,
@@ -296,6 +300,13 @@ interface RemoteUpdateFileOutput {
296
300
 
297
301
  export const vfRemoteUpdateFile: MCPTool<RemoteUpdateFileInput, RemoteUpdateFileOutput> = {
298
302
  name: "vf_remote_update_file",
303
+ title: "Update Remote File",
304
+ annotations: {
305
+ readOnlyHint: false,
306
+ destructiveHint: true,
307
+ idempotentHint: true,
308
+ openWorldHint: false,
309
+ },
299
310
  description:
300
311
  "Create or update a file in a remote Veryfront project. Always read the file first before updating to understand its current state.",
301
312
  inputSchema: remoteUpdateFileInput,
@@ -341,6 +352,13 @@ interface RemoteDeleteFileOutput {
341
352
 
342
353
  export const vfRemoteDeleteFile: MCPTool<RemoteDeleteFileInput, RemoteDeleteFileOutput> = {
343
354
  name: "vf_remote_delete_file",
355
+ title: "Delete Remote File",
356
+ annotations: {
357
+ readOnlyHint: false,
358
+ destructiveHint: true,
359
+ idempotentHint: true,
360
+ openWorldHint: false,
361
+ },
344
362
  description: "Delete a file from a remote Veryfront project.",
345
363
  inputSchema: remoteDeleteFileInput,
346
364
  execute: async (input) => {
@@ -378,6 +396,8 @@ interface RemoteSearchFilesOutput {
378
396
 
379
397
  export const vfRemoteSearchFiles: MCPTool<RemoteSearchFilesInput, RemoteSearchFilesOutput> = {
380
398
  name: "vf_remote_search_files",
399
+ title: "Search Remote Files",
400
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
381
401
  description:
382
402
  "Search for text patterns within file contents in a remote Veryfront project. Supports regex and glob patterns.",
383
403
  inputSchema: remoteSearchFilesInput,
@@ -430,6 +450,8 @@ interface RemoteMoveFileOutput {
430
450
 
431
451
  export const vfRemoteMoveFile: MCPTool<RemoteMoveFileInput, RemoteMoveFileOutput> = {
432
452
  name: "vf_remote_move_file",
453
+ title: "Move Remote File",
454
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
433
455
  description: "Move or rename a file in a remote Veryfront project.",
434
456
  inputSchema: remoteMoveFileInput,
435
457
  execute: async (input) => {
@@ -477,6 +499,8 @@ interface RemoteListBranchesOutput {
477
499
 
478
500
  export const vfRemoteListBranches: MCPTool<RemoteListBranchesInput, RemoteListBranchesOutput> = {
479
501
  name: "vf_remote_list_branches",
502
+ title: "List Remote Branches",
503
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
480
504
  description: "List branches in a remote Veryfront project.",
481
505
  inputSchema: remoteListBranchesInput,
482
506
  execute: async (input) => {
@@ -514,6 +538,8 @@ interface RemoteCreateBranchOutput {
514
538
 
515
539
  export const vfRemoteCreateBranch: MCPTool<RemoteCreateBranchInput, RemoteCreateBranchOutput> = {
516
540
  name: "vf_remote_create_branch",
541
+ title: "Create Remote Branch",
542
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
517
543
  description:
518
544
  "Create a new branch in a remote Veryfront project. Branch from main by default, or specify a base branch.",
519
545
  inputSchema: remoteCreateBranchInput,
@@ -552,6 +578,8 @@ interface RemoteMergeBranchOutput {
552
578
 
553
579
  export const vfRemoteMergeBranch: MCPTool<RemoteMergeBranchInput, RemoteMergeBranchOutput> = {
554
580
  name: "vf_remote_merge_branch",
581
+ title: "Merge Remote Branch",
582
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
555
583
  description: "Merge a branch into the target branch (or main if not specified).",
556
584
  inputSchema: remoteMergeBranchInput,
557
585
  execute: async (input) => {
@@ -596,6 +624,13 @@ interface RemoteDeleteBranchOutput {
596
624
 
597
625
  export const vfRemoteDeleteBranch: MCPTool<RemoteDeleteBranchInput, RemoteDeleteBranchOutput> = {
598
626
  name: "vf_remote_delete_branch",
627
+ title: "Delete Remote Branch",
628
+ annotations: {
629
+ readOnlyHint: false,
630
+ destructiveHint: true,
631
+ idempotentHint: true,
632
+ openWorldHint: false,
633
+ },
599
634
  description: "Delete a branch from a remote Veryfront project.",
600
635
  inputSchema: remoteDeleteBranchInput,
601
636
  execute: async (input) => {
@@ -633,6 +668,8 @@ interface RemoteCreateProjectOutput {
633
668
 
634
669
  export const vfRemoteCreateProject: MCPTool<RemoteCreateProjectInput, RemoteCreateProjectOutput> = {
635
670
  name: "vf_remote_create_project",
671
+ title: "Create Remote Project",
672
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
636
673
  description: "Create a new Veryfront project. Returns the project details including ID and slug.",
637
674
  inputSchema: remoteCreateProjectInput,
638
675
  execute: async (input) => {
@@ -671,6 +708,8 @@ interface RemoteCloneProjectOutput {
671
708
 
672
709
  export const vfRemoteCloneProject: MCPTool<RemoteCloneProjectInput, RemoteCloneProjectOutput> = {
673
710
  name: "vf_remote_clone_project",
711
+ title: "Clone Remote Project",
712
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
674
713
  description:
675
714
  "Clone a Veryfront project by creating a new project and copying all files from the source.",
676
715
  inputSchema: remoteCloneProjectInput,
@@ -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");