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
@@ -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();
@@ -35,18 +35,24 @@ export function setServerStartTime(time: number): void {
35
35
 
36
36
  const getErrorsInput = z.object({
37
37
  type: z.enum(["compile", "runtime", "bundle", "hmr", "module"]).optional().describe(
38
- "Filter by error type",
38
+ "Filter by error type. Example: 'compile'. Omit to return all types.",
39
+ ),
40
+ file: z.string().optional().describe(
41
+ "Filter by file path. Example: 'app/page.tsx'. Omit to return errors from all files.",
42
+ ),
43
+ limit: z.number().optional().default(50).describe(
44
+ "Maximum number of errors to return. Defaults to 50.",
39
45
  ),
40
- file: z.string().optional().describe("Filter by file path"),
41
- limit: z.number().optional().default(50).describe("Maximum number of errors to return"),
42
46
  });
43
47
 
44
48
  type GetErrorsInput = z.infer<typeof getErrorsInput>;
45
49
 
46
50
  export const vfGetErrors: MCPTool<GetErrorsInput, DevError[]> = {
47
51
  name: "vf_get_errors",
52
+ title: "Get Errors",
53
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
48
54
  description:
49
- "Get compilation, runtime, and build errors from the dev server. Use this to debug issues with your code.",
55
+ "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.",
50
56
  inputSchema: getErrorsInput,
51
57
  execute: async (input) => {
52
58
  const errors = getErrorCollector().getAll({
@@ -60,21 +66,31 @@ export const vfGetErrors: MCPTool<GetErrorsInput, DevError[]> = {
60
66
  };
61
67
 
62
68
  const getLogsInput = z.object({
63
- level: z.enum(["debug", "info", "warn", "error"]).optional().describe("Filter by log level"),
69
+ level: z.enum(["debug", "info", "warn", "error"]).optional().describe(
70
+ "Filter by log level. Example: 'error'. Omit to return all levels.",
71
+ ),
64
72
  source: z.string().optional().describe(
65
- "Filter by log source (e.g., 'server', 'hmr', 'transform')",
73
+ "Filter by log source. Example: 'server', 'hmr', 'transform'. Omit to return all sources.",
74
+ ),
75
+ pattern: z.string().optional().describe(
76
+ "Filter by pattern (case-insensitive substring match). Example: 'timeout'.",
77
+ ),
78
+ limit: z.number().optional().default(100).describe(
79
+ "Maximum number of log entries to return. Defaults to 100.",
80
+ ),
81
+ since: z.number().optional().describe(
82
+ "Only return logs after this Unix timestamp in milliseconds.",
66
83
  ),
67
- pattern: z.string().optional().describe("Filter by pattern (case-insensitive substring match)"),
68
- limit: z.number().optional().default(100).describe("Maximum number of log entries to return"),
69
- since: z.number().optional().describe("Only return logs after this timestamp"),
70
84
  });
71
85
 
72
86
  type GetLogsInput = z.infer<typeof getLogsInput>;
73
87
 
74
88
  export const vfGetLogs: MCPTool<GetLogsInput, LogEntry[]> = {
75
89
  name: "vf_get_logs",
90
+ title: "Get Logs",
91
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
76
92
  description:
77
- "Get recent server logs. Use this to understand what the server is doing and debug runtime issues.",
93
+ "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.",
78
94
  inputSchema: getLogsInput,
79
95
  execute: async (input) => {
80
96
  return getLogBuffer().query({
@@ -89,7 +105,7 @@ export const vfGetLogs: MCPTool<GetLogsInput, LogEntry[]> = {
89
105
 
90
106
  const clearCacheInput = z.object({
91
107
  type: z.enum(["all", "modules", "mdx"]).optional().default("all").describe(
92
- "Type of cache to clear",
108
+ "Type of cache to clear. Example: 'modules'. Defaults to 'all'.",
93
109
  ),
94
110
  });
95
111
 
@@ -102,8 +118,15 @@ interface ClearCacheOutput {
102
118
 
103
119
  export const vfClearCache: MCPTool<ClearCacheInput, ClearCacheOutput> = {
104
120
  name: "vf_clear_cache",
121
+ title: "Clear Cache",
122
+ annotations: {
123
+ readOnlyHint: false,
124
+ destructiveHint: true,
125
+ idempotentHint: true,
126
+ openWorldHint: false,
127
+ },
105
128
  description:
106
- "Clear module and build caches. Use this when changes aren't being reflected or to force a rebuild.",
129
+ "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.",
107
130
  inputSchema: clearCacheInput,
108
131
  execute: async (input) => {
109
132
  const fs = createFileSystem();
@@ -143,7 +166,10 @@ export function createVfGetStatus(
143
166
  ): MCPTool<GetStatusInput, ServerStatus> {
144
167
  return {
145
168
  name: "vf_get_status",
146
- description: "Get the current status of the dev server including error counts and uptime.",
169
+ title: "Server Status",
170
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
171
+ description:
172
+ "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.",
147
173
  inputSchema: getStatusInput,
148
174
  execute: async () => {
149
175
  const errors = getErrorCollector();
@@ -167,9 +193,11 @@ export function createVfGetStatus(
167
193
  export const vfGetStatus = createVfGetStatus();
168
194
 
169
195
  const clearErrorsInput = z.object({
170
- file: z.string().optional().describe("Clear errors for a specific file only"),
196
+ file: z.string().optional().describe(
197
+ "Clear errors for a specific file only. Example: 'app/page.tsx'. Omit to clear all files.",
198
+ ),
171
199
  type: z.enum(["compile", "runtime", "bundle", "hmr", "module"]).optional().describe(
172
- "Clear errors of a specific type only",
200
+ "Clear errors of a specific type only. Example: 'compile'. Omit to clear all types.",
173
201
  ),
174
202
  });
175
203
 
@@ -181,7 +209,15 @@ interface ClearErrorsOutput {
181
209
 
182
210
  export const vfClearErrors: MCPTool<ClearErrorsInput, ClearErrorsOutput> = {
183
211
  name: "vf_clear_errors",
184
- description: "Clear errors from the error collector. Useful after fixing issues.",
212
+ title: "Clear Errors",
213
+ annotations: {
214
+ readOnlyHint: false,
215
+ destructiveHint: true,
216
+ idempotentHint: true,
217
+ openWorldHint: false,
218
+ },
219
+ description:
220
+ "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.",
185
221
  inputSchema: clearErrorsInput,
186
222
  execute: async (input) => {
187
223
  const collector = getErrorCollector();
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.140",
3
+ "version": "0.1.142",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -406,7 +406,7 @@ export class AgentRuntime {
406
406
  logger.error("Agent stream error", { error });
407
407
  sendSSE(controller, encoder, {
408
408
  type: "error",
409
- error: "An internal error occurred",
409
+ error: error instanceof Error ? error.message : String(error),
410
410
  });
411
411
  closeSSEStream(controller);
412
412
  } finally {
@@ -11,6 +11,7 @@ import { executeTool, toolRegistry } from "../../tool/index.js";
11
11
  import { toolToProviderDefinition } from "../../tool/registry.js";
12
12
  import { SKILL_TOOL_IDS } from "../../skill/types.js";
13
13
  import { serverLogger } from "../../utils/index.js";
14
+ import { createError, toError } from "../../errors/veryfront-error.js";
14
15
  import {
15
16
  executeRemoteIntegrationTool,
16
17
  isRemoteIntegrationTool,
@@ -71,6 +72,52 @@ export function isDynamicTool(name: string): boolean {
71
72
  // deno-lint-ignore no-explicit-any -- generic erasure: accepts Tool with any input/output types
72
73
  export type ToolConfigEntry = Tool<any, any> | boolean;
73
74
 
75
+ function formatAvailableToolNames(names: Iterable<string>): string {
76
+ const sorted = [...new Set(names)].sort();
77
+ return sorted.length > 0 ? sorted.join(", ") : "(none)";
78
+ }
79
+
80
+ function throwUnknownConfiguredToolsError(
81
+ unknownToolNames: string[],
82
+ availableLocalToolNames: Iterable<string>,
83
+ availableRemoteToolNames: Iterable<string>,
84
+ ): never {
85
+ const unknownList = unknownToolNames.sort().join(", ");
86
+ const availableNames = formatAvailableToolNames([
87
+ ...availableLocalToolNames,
88
+ ...availableRemoteToolNames,
89
+ ]);
90
+
91
+ throw toError(
92
+ createError({
93
+ type: "agent",
94
+ message:
95
+ `Unknown tool reference${unknownToolNames.length === 1 ? "" : "s"}: ${unknownList}. ` +
96
+ `Tool names must exactly match tool({ id: "..." }). Available tools: ${availableNames}`,
97
+ }),
98
+ );
99
+ }
100
+
101
+ async function getRemoteToolDefinitions(options?: {
102
+ includeIntegrationTools?: boolean;
103
+ allowedRemoteToolNames?: string[];
104
+ }): Promise<ToolDefinition[]> {
105
+ if (options?.includeIntegrationTools === false) {
106
+ return [];
107
+ }
108
+
109
+ try {
110
+ const { getRemoteIntegrationToolDefinitions } = await import(
111
+ "../../integrations/remote-tools.js"
112
+ );
113
+ return (await getRemoteIntegrationToolDefinitions()).filter((def) =>
114
+ !options?.allowedRemoteToolNames || options.allowedRemoteToolNames.includes(def.name)
115
+ );
116
+ } catch {
117
+ return [];
118
+ }
119
+ }
120
+
74
121
  export function resolveConfiguredTool(
75
122
  toolsConfig: true | Record<string, ToolConfigEntry> | undefined,
76
123
  toolName: string,
@@ -179,32 +226,35 @@ export async function getAvailableTools(
179
226
  });
180
227
 
181
228
  // Append remote integration tools (per-request, project-scoped)
182
- if (options?.includeIntegrationTools !== false) {
183
- try {
184
- const { getRemoteIntegrationToolDefinitions } = await import(
185
- "../../integrations/remote-tools.js"
186
- );
187
- const remoteDefs = (await getRemoteIntegrationToolDefinitions()).filter((def) =>
188
- !options?.allowedRemoteToolNames || options.allowedRemoteToolNames.includes(def.name)
189
- );
190
- for (const def of remoteDefs) {
191
- logToolDefinition(def.name, def);
192
- }
193
- tools.push(...remoteDefs);
194
- } catch {
195
- // Integration tools unavailable — non-fatal
196
- }
229
+ const remoteDefs = await getRemoteToolDefinitions(options);
230
+ for (const def of remoteDefs) {
231
+ logToolDefinition(def.name, def);
197
232
  }
233
+ tools.push(...remoteDefs);
198
234
 
199
235
  return tools;
200
236
  }
201
237
 
202
238
  const tools: ToolDefinition[] = [];
239
+ const remoteDefs = await getRemoteToolDefinitions(options);
240
+ const remoteToolNames = new Set(remoteDefs.map((def) => def.name));
241
+ const explicitlyRequestedRemoteToolNames = new Set<string>();
242
+ const unresolvedConfiguredToolNames: string[] = [];
203
243
 
204
244
  for (const [name, entry] of Object.entries(toolsConfig)) {
205
245
  if (entry === true) {
206
246
  const tool = toolRegistry.get(name);
207
- if (tool) addToolDefinition(tools, name, tool);
247
+ if (tool) {
248
+ addToolDefinition(tools, name, tool);
249
+ continue;
250
+ }
251
+
252
+ if (remoteToolNames.has(name)) {
253
+ explicitlyRequestedRemoteToolNames.add(name);
254
+ continue;
255
+ }
256
+
257
+ unresolvedConfiguredToolNames.push(name);
208
258
  continue;
209
259
  }
210
260
 
@@ -213,28 +263,28 @@ export async function getAvailableTools(
213
263
  }
214
264
  }
215
265
 
216
- // Also append remote integration tools for explicit-object configs.
217
- // The internal streaming path converts `tools: true` to an explicit object
218
- // from the local registry, so remote tools would be missed without this.
219
- if (options?.includeIntegrationTools !== false) {
220
- try {
221
- const { getRemoteIntegrationToolDefinitions } = await import(
222
- "../../integrations/remote-tools.js"
223
- );
224
- const remoteDefs = (await getRemoteIntegrationToolDefinitions()).filter((def) =>
225
- !options?.allowedRemoteToolNames || options.allowedRemoteToolNames.includes(def.name)
226
- );
227
- for (const def of remoteDefs) {
228
- // Skip if already present (e.g., explicitly configured by name)
229
- if (!tools.some((t) => t.name === def.name)) {
230
- logToolDefinition(def.name, def);
231
- tools.push(def);
232
- }
233
- }
234
- } catch {
235
- // Integration tools unavailable — non-fatal
266
+ // Explicit-object configs should only expose remote definitions that were
267
+ // explicitly requested, except for the internal runtime path that expands
268
+ // `tools: true` into an explicit local-tool map and passes the remote allowlist.
269
+ const remoteDefsToAppend = explicitlyRequestedRemoteToolNames.size > 0
270
+ ? remoteDefs.filter((def) => explicitlyRequestedRemoteToolNames.has(def.name))
271
+ : remoteDefs.filter((def) => options?.allowedRemoteToolNames?.includes(def.name));
272
+
273
+ for (const def of remoteDefsToAppend) {
274
+ // Skip if already present (e.g., explicitly configured by name)
275
+ if (!tools.some((t) => t.name === def.name)) {
276
+ logToolDefinition(def.name, def);
277
+ tools.push(def);
236
278
  }
237
279
  }
238
280
 
281
+ if (unresolvedConfiguredToolNames.length > 0) {
282
+ throwUnknownConfiguredToolsError(
283
+ unresolvedConfiguredToolNames,
284
+ toolRegistry.getAll().keys(),
285
+ remoteToolNames,
286
+ );
287
+ }
288
+
239
289
  return tools;
240
290
  }
@@ -17,8 +17,10 @@ import {
17
17
  } from "./ag-ui-sse.js";
18
18
  import { AgentRunCancelledError, type AgentRunSessionManager } from "./session-manager.js";
19
19
  import type { RuntimeRunAgentInput } from "./schema.js";
20
+ import { serverLogger } from "../utils/index.js";
20
21
 
21
22
  const anyObjectSchema = z.record(z.string(), z.unknown());
23
+ const logger = serverLogger.component("internal-agent-run-stream");
22
24
 
23
25
  type RuntimeFilteredAgent = Agent & {
24
26
  config: Agent["config"] & {
@@ -230,6 +232,14 @@ export async function createRuntimeAgentStreamResponse(
230
232
  agent: Agent,
231
233
  deps: RuntimeAgentStreamExecutionDeps,
232
234
  ): Promise<dntShim.Response> {
235
+ logger.info("Starting internal agent runtime stream", {
236
+ runId: input.runId,
237
+ threadId: input.threadId,
238
+ agentId: input.agentId,
239
+ messageCount: input.messages.length,
240
+ toolCount: input.tools.length,
241
+ contextCount: input.context.length,
242
+ });
233
243
  const abortSignal = deps.sessionManager.startRun({
234
244
  runId: input.runId,
235
245
  threadId: input.threadId,
@@ -272,8 +282,19 @@ export async function createRuntimeAgentStreamResponse(
272
282
  undefined,
273
283
  abortSignal,
274
284
  );
285
+ logger.info("Internal agent runtime stream attached", {
286
+ runId: input.runId,
287
+ threadId: input.threadId,
288
+ agentId: input.agentId,
289
+ });
275
290
  } catch (error) {
276
291
  deps.sessionManager.failRun(input.runId);
292
+ logger.error("Internal agent runtime stream setup failed", {
293
+ runId: input.runId,
294
+ threadId: input.threadId,
295
+ agentId: input.agentId,
296
+ error: error instanceof Error ? error.message : String(error),
297
+ });
277
298
  throw error;
278
299
  }
279
300
 
@@ -306,6 +327,11 @@ export async function createRuntimeAgentStreamResponse(
306
327
 
307
328
  const abortHandler = () => {
308
329
  aborted = true;
330
+ logger.warn("Internal agent runtime stream aborted", {
331
+ runId: input.runId,
332
+ threadId: input.threadId,
333
+ agentId: input.agentId,
334
+ });
309
335
  reader.cancel(new AgentRunCancelledError()).catch(() => {});
310
336
  };
311
337
 
@@ -324,6 +350,11 @@ export async function createRuntimeAgentStreamResponse(
324
350
  throwIfAborted();
325
351
 
326
352
  if (done) {
353
+ logger.info("Internal agent runtime stream reader completed", {
354
+ runId: input.runId,
355
+ threadId: input.threadId,
356
+ agentId: input.agentId,
357
+ });
327
358
  break;
328
359
  }
329
360
 
@@ -353,15 +384,35 @@ export async function createRuntimeAgentStreamResponse(
353
384
  enqueueIfAttached(mappedEvent.event, mappedEvent.payload);
354
385
  }
355
386
  deps.sessionManager.completeRun(input.runId);
387
+ logger.info("Internal agent runtime stream finalized", {
388
+ runId: input.runId,
389
+ threadId: input.threadId,
390
+ agentId: input.agentId,
391
+ sawVisibleOutput: state.sawVisibleOutput,
392
+ sawTerminalError: state.sawTerminalError,
393
+ finishReason: state.metadata.finishReason,
394
+ });
356
395
  } catch (error) {
357
396
  if (error instanceof AgentRunCancelledError) {
358
397
  deps.sessionManager.cancelRun(input.runId);
398
+ logger.warn("Internal agent runtime stream cancelled", {
399
+ runId: input.runId,
400
+ threadId: input.threadId,
401
+ agentId: input.agentId,
402
+ error: error.message,
403
+ });
359
404
  enqueueIfAttached("RunError", {
360
405
  code: "CANCELLED",
361
406
  message: error.message,
362
407
  });
363
408
  } else {
364
409
  deps.sessionManager.failRun(input.runId);
410
+ logger.error("Internal agent runtime stream failed", {
411
+ runId: input.runId,
412
+ threadId: input.threadId,
413
+ agentId: input.agentId,
414
+ error: error instanceof Error ? error.message : String(error),
415
+ });
365
416
  enqueueIfAttached("RunError", {
366
417
  code: "RUNTIME_ERROR",
367
418
  message: error instanceof Error ? error.message : String(error),
@@ -372,10 +423,21 @@ export async function createRuntimeAgentStreamResponse(
372
423
  if (clientAttached) {
373
424
  controller.close();
374
425
  }
426
+ logger.debug("Internal agent runtime stream response closed", {
427
+ runId: input.runId,
428
+ threadId: input.threadId,
429
+ agentId: input.agentId,
430
+ clientAttached,
431
+ });
375
432
  }
376
433
  },
377
434
  cancel() {
378
435
  clientAttached = false;
436
+ logger.info("Internal agent runtime client detached", {
437
+ runId: input.runId,
438
+ threadId: input.threadId,
439
+ agentId: input.agentId,
440
+ });
379
441
  return Promise.resolve();
380
442
  },
381
443
  });
@@ -53,8 +53,11 @@ type IssuesCreateInput = z.infer<typeof issuesCreateInput>;
53
53
 
54
54
  const issuesCreate: MCPTool<IssuesCreateInput, Issue> = {
55
55
  name: "issues_create",
56
- description: "Create a new issue, task, or plan as a markdown file. " +
57
- "Use prefix 'TASK' for small work items, 'PLAN' for proposals/RFCs, 'ISSUE' for bugs/features.",
56
+ title: "Create Issue",
57
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
58
+ description: "Use this when you need to create a new issue, task, or plan as a markdown file. " +
59
+ "Use prefix 'TASK' for small work items, 'PLAN' for proposals/RFCs, 'ISSUE' for bugs/features. " +
60
+ "Returns the created issue. Do not use for updating — use issues_update instead.",
58
61
  inputSchema: issuesCreateInput,
59
62
  execute: async (input) => {
60
63
  const manager = getManager(input.projectDir);
@@ -81,7 +84,10 @@ type IssuesGetInput = z.infer<typeof issuesGetInput>;
81
84
 
82
85
  const issuesGet: MCPTool<IssuesGetInput, Issue | null> = {
83
86
  name: "issues_get",
84
- description: "Get a specific issue by ID. Returns null if not found.",
87
+ title: "Get Issue",
88
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
89
+ description:
90
+ "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.",
85
91
  inputSchema: issuesGetInput,
86
92
  execute: async (input) => {
87
93
  const manager = getManager(input.projectDir);
@@ -107,8 +113,16 @@ type IssuesUpdateInput = z.infer<typeof issuesUpdateInput>;
107
113
 
108
114
  const issuesUpdate: MCPTool<IssuesUpdateInput, Issue | null> = {
109
115
  name: "issues_update",
110
- description: "Update an existing issue. Only provided fields are updated. " +
111
- "Returns the updated issue or null if not found.",
116
+ title: "Update Issue",
117
+ annotations: {
118
+ readOnlyHint: false,
119
+ destructiveHint: false,
120
+ idempotentHint: true,
121
+ openWorldHint: false,
122
+ },
123
+ description:
124
+ "Use this when you need to modify an existing issue. Only provided fields are updated. " +
125
+ "Returns the updated issue or null if not found. Do not use to close — use issues_close instead.",
112
126
  inputSchema: issuesUpdateInput,
113
127
  execute: async (input) => {
114
128
  const manager = getManager(input.projectDir);
@@ -153,8 +167,10 @@ interface IssuesListOutput {
153
167
 
154
168
  const issuesList: MCPTool<IssuesListInput, IssuesListOutput> = {
155
169
  name: "issues_list",
156
- description: "List issues with filtering and sorting. " +
157
- "Returns matching issues and total count.",
170
+ title: "List Issues",
171
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
172
+ description: "Use this when you need to find issues matching criteria. " +
173
+ "Returns matching issues and total count. Do not use to get a single known issue — use issues_get instead.",
158
174
  inputSchema: issuesListInput,
159
175
  execute: async (input) => {
160
176
  const manager = getManager(input.projectDir);
@@ -183,7 +199,15 @@ type IssuesCloseInput = z.infer<typeof issuesCloseInput>;
183
199
 
184
200
  const issuesClose: MCPTool<IssuesCloseInput, Issue | null> = {
185
201
  name: "issues_close",
186
- description: "Close an issue. Returns the updated issue or null if not found.",
202
+ title: "Close Issue",
203
+ annotations: {
204
+ readOnlyHint: false,
205
+ destructiveHint: false,
206
+ idempotentHint: true,
207
+ openWorldHint: false,
208
+ },
209
+ description:
210
+ "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.",
187
211
  inputSchema: issuesCloseInput,
188
212
  execute: async (input) => {
189
213
  const manager = getManager(input.projectDir);
@@ -207,8 +231,17 @@ interface IssuesDeleteOutput {
207
231
 
208
232
  const issuesDelete: MCPTool<IssuesDeleteInput, IssuesDeleteOutput> = {
209
233
  name: "issues_delete",
210
- description: "Permanently delete an issue file. " +
211
- "Use with caution - this cannot be undone.",
234
+ title: "Delete Issue",
235
+ annotations: {
236
+ readOnlyHint: false,
237
+ destructiveHint: true,
238
+ idempotentHint: true,
239
+ openWorldHint: false,
240
+ },
241
+ description:
242
+ "Use this when you need to permanently delete an issue. Returns {deleted: true/false}. " +
243
+ "WARNING: this is irreversible and cannot be undone. Prefer issues_close unless permanent deletion is explicitly requested. " +
244
+ "Do not use to close — use issues_close instead.",
212
245
  inputSchema: issuesDeleteInput,
213
246
  execute: async (input) => {
214
247
  const manager = getManager(input.projectDir);
@@ -24,7 +24,13 @@
24
24
  import "../../_dnt.polyfills.js";
25
25
 
26
26
 
27
- export type { MCPServerConfig, MCPStats, MCPTool } from "./types.js";
27
+ export type {
28
+ MCPServerConfig,
29
+ MCPStats,
30
+ MCPTool,
31
+ ToolAnnotations,
32
+ ToolListEntry,
33
+ } from "./types.js";
28
34
 
29
35
  export {
30
36
  clearMCPRegistry,