veryfront 0.1.146 → 0.1.147
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.
- package/esm/deno.js +1 -1
- package/esm/src/internal-agents/run-stream.d.ts.map +1 -1
- package/esm/src/internal-agents/run-stream.js +60 -79
- package/esm/src/internal-agents/schema.d.ts +243 -4
- package/esm/src/internal-agents/schema.d.ts.map +1 -1
- package/esm/src/internal-agents/schema.js +219 -8
- package/esm/src/jobs/schemas.d.ts +4 -4
- package/esm/src/mcp/elicitation.d.ts +16 -0
- package/esm/src/mcp/elicitation.d.ts.map +1 -0
- package/esm/src/mcp/elicitation.js +21 -0
- package/esm/src/mcp/index.d.ts +3 -0
- package/esm/src/mcp/index.d.ts.map +1 -1
- package/esm/src/mcp/index.js +2 -0
- package/esm/src/mcp/server.d.ts +22 -0
- package/esm/src/mcp/server.d.ts.map +1 -1
- package/esm/src/mcp/server.js +163 -2
- package/esm/src/mcp/task-store.d.ts +27 -0
- package/esm/src/mcp/task-store.d.ts.map +1 -0
- package/esm/src/mcp/task-store.js +116 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/memory.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/cache/memory.js +4 -2
- package/esm/src/server/handlers/request/agent-stream.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/request/agent-stream.handler.js +4 -3
- package/esm/src/tool/remote-mcp.d.ts.map +1 -1
- package/esm/src/tool/remote-mcp.js +60 -1
- package/esm/src/tool/types.d.ts +2 -0
- package/esm/src/tool/types.d.ts.map +1 -1
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/internal-agents/run-stream.ts +61 -94
- package/src/src/internal-agents/schema.ts +277 -10
- package/src/src/mcp/elicitation.ts +42 -0
- package/src/src/mcp/index.ts +9 -0
- package/src/src/mcp/server.ts +185 -2
- package/src/src/mcp/task-store.ts +137 -0
- package/src/src/modules/react-loader/ssr-module-loader/cache/memory.ts +4 -2
- package/src/src/server/handlers/request/agent-stream.handler.ts +5 -3
- package/src/src/tool/remote-mcp.ts +86 -1
- package/src/src/tool/types.ts +2 -0
- package/src/src/utils/version-constant.ts +1 -1
|
@@ -79,21 +79,105 @@ export const RuntimeAgentSourceContextSchema = z.discriminatedUnion("type", [
|
|
|
79
79
|
}),
|
|
80
80
|
]);
|
|
81
81
|
|
|
82
|
+
const RuntimeMessageExtensionFieldsSchema = {
|
|
83
|
+
name: z.string().max(256).optional(),
|
|
84
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
85
|
+
createdAt: z.string().optional(),
|
|
86
|
+
} as const;
|
|
87
|
+
|
|
88
|
+
export const RuntimeToolFunctionCallSchema = z.object({
|
|
89
|
+
name: ClientToolNameSchema,
|
|
90
|
+
arguments: z.string().max(MAX_TOOL_PARAMETERS_BYTES),
|
|
91
|
+
}).strict();
|
|
92
|
+
|
|
93
|
+
export const RuntimeToolCallSchema = z.object({
|
|
94
|
+
id: z.string().min(1).max(128),
|
|
95
|
+
type: z.literal("function"),
|
|
96
|
+
function: RuntimeToolFunctionCallSchema,
|
|
97
|
+
}).strict();
|
|
98
|
+
|
|
99
|
+
export const RuntimeSystemMessageSchema = z.object({
|
|
100
|
+
id: z.string().min(1),
|
|
101
|
+
role: z.literal("system"),
|
|
102
|
+
content: z.string(),
|
|
103
|
+
...RuntimeMessageExtensionFieldsSchema,
|
|
104
|
+
}).strict();
|
|
105
|
+
|
|
106
|
+
export const RuntimeUserMessageSchema = z.object({
|
|
107
|
+
id: z.string().min(1),
|
|
108
|
+
role: z.literal("user"),
|
|
109
|
+
content: z.string(),
|
|
110
|
+
...RuntimeMessageExtensionFieldsSchema,
|
|
111
|
+
}).strict();
|
|
112
|
+
|
|
113
|
+
export const RuntimeAssistantMessageSchema = z.object({
|
|
114
|
+
id: z.string().min(1),
|
|
115
|
+
role: z.literal("assistant"),
|
|
116
|
+
content: z.string().optional(),
|
|
117
|
+
toolCalls: z.array(RuntimeToolCallSchema).optional(),
|
|
118
|
+
...RuntimeMessageExtensionFieldsSchema,
|
|
119
|
+
}).strict();
|
|
120
|
+
|
|
121
|
+
export const RuntimeToolMessageSchema = z.object({
|
|
122
|
+
id: z.string().min(1),
|
|
123
|
+
role: z.literal("tool"),
|
|
124
|
+
toolCallId: z.string().min(1).max(128),
|
|
125
|
+
content: z.string(),
|
|
126
|
+
error: z.string().optional(),
|
|
127
|
+
...RuntimeMessageExtensionFieldsSchema,
|
|
128
|
+
}).strict();
|
|
129
|
+
|
|
130
|
+
export const RuntimeMessageSchema = z.discriminatedUnion("role", [
|
|
131
|
+
RuntimeSystemMessageSchema,
|
|
132
|
+
RuntimeUserMessageSchema,
|
|
133
|
+
RuntimeAssistantMessageSchema,
|
|
134
|
+
RuntimeToolMessageSchema,
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
export const RuntimeContextSchema = z.union([
|
|
138
|
+
z.object({
|
|
139
|
+
description: z.string().max(1024),
|
|
140
|
+
value: z.string().max(MAX_CONTEXT_ITEM_BYTES),
|
|
141
|
+
}),
|
|
142
|
+
RuntimeContextItemSchema,
|
|
143
|
+
]);
|
|
144
|
+
|
|
82
145
|
export const RuntimeRunAgentInputSchema = z.object({
|
|
146
|
+
threadId: z.string().uuid(),
|
|
147
|
+
runId: RunIdSchema,
|
|
148
|
+
parentRunId: RunIdSchema.optional(),
|
|
149
|
+
state: z.unknown().optional(),
|
|
150
|
+
messages: z.array(RuntimeMessageSchema).max(MAX_RUNTIME_MESSAGES),
|
|
151
|
+
tools: z.array(RuntimeInjectedToolSchema).max(50).default([]),
|
|
152
|
+
context: z.array(RuntimeContextSchema).max(10).default([]).refine(
|
|
153
|
+
(value) => isWithinJsonSizeLimit(value, MAX_CONTEXT_TOTAL_BYTES),
|
|
154
|
+
{ message: "context must be less than 64 KB total" },
|
|
155
|
+
),
|
|
156
|
+
forwardedProps: z.record(z.string(), z.unknown()).optional().refine(
|
|
157
|
+
(value) => value === undefined || isWithinJsonSizeLimit(value, MAX_FORWARDED_PROPS_BYTES),
|
|
158
|
+
{ message: "forwardedProps must be less than 64 KB" },
|
|
159
|
+
),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
export const InternalAgentCompatibilityMessageSchema = z.object({
|
|
163
|
+
id: z.string().min(1),
|
|
164
|
+
role: z.enum(["user", "assistant", "system", "tool"]),
|
|
165
|
+
parts: z.array(z.object({ type: z.string().min(1) }).passthrough()).default([]),
|
|
166
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
167
|
+
createdAt: z.string().optional(),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
export const InternalAgentStreamRequestSchema = z.object({
|
|
83
171
|
agentId: AgentIdSchema,
|
|
84
172
|
threadId: z.string().uuid(),
|
|
85
173
|
runId: RunIdSchema,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
92
|
-
createdAt: z.string().optional(),
|
|
93
|
-
}),
|
|
94
|
-
).max(MAX_RUNTIME_MESSAGES),
|
|
174
|
+
parentRunId: RunIdSchema.optional(),
|
|
175
|
+
state: z.unknown().optional(),
|
|
176
|
+
messages: z.array(z.union([RuntimeMessageSchema, InternalAgentCompatibilityMessageSchema])).max(
|
|
177
|
+
MAX_RUNTIME_MESSAGES,
|
|
178
|
+
),
|
|
95
179
|
tools: z.array(RuntimeInjectedToolSchema).max(50).default([]),
|
|
96
|
-
context: z.array(
|
|
180
|
+
context: z.array(RuntimeContextSchema).max(10).default([]).refine(
|
|
97
181
|
(value) => isWithinJsonSizeLimit(value, MAX_CONTEXT_TOTAL_BYTES),
|
|
98
182
|
{ message: "context must be less than 64 KB total" },
|
|
99
183
|
),
|
|
@@ -104,6 +188,188 @@ export const RuntimeRunAgentInputSchema = z.object({
|
|
|
104
188
|
),
|
|
105
189
|
});
|
|
106
190
|
|
|
191
|
+
type RuntimeMessage = z.infer<typeof RuntimeMessageSchema>;
|
|
192
|
+
type InternalAgentCompatibilityMessage = z.infer<typeof InternalAgentCompatibilityMessageSchema>;
|
|
193
|
+
|
|
194
|
+
function extractToolArgs(
|
|
195
|
+
part: Record<string, unknown>,
|
|
196
|
+
): Record<string, unknown> {
|
|
197
|
+
const args = part.args;
|
|
198
|
+
if (args && typeof args === "object" && !Array.isArray(args)) {
|
|
199
|
+
return args as Record<string, unknown>;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const input = part.input;
|
|
203
|
+
if (input && typeof input === "object" && !Array.isArray(input)) {
|
|
204
|
+
return input as Record<string, unknown>;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function serializeToolArguments(args: Record<string, unknown>): string {
|
|
211
|
+
try {
|
|
212
|
+
return JSON.stringify(args);
|
|
213
|
+
} catch {
|
|
214
|
+
return "{}";
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function getPartString(
|
|
219
|
+
part: Record<string, unknown>,
|
|
220
|
+
...keys: string[]
|
|
221
|
+
): string | null {
|
|
222
|
+
for (const key of keys) {
|
|
223
|
+
const value = part[key];
|
|
224
|
+
if (typeof value === "string" && value.length > 0) {
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function isLegacyToolCallPart(part: Record<string, unknown>): boolean {
|
|
233
|
+
return getPartString(part, "type") === "tool_call";
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function isCanonicalToolCallPart(part: Record<string, unknown>): boolean {
|
|
237
|
+
const type = getPartString(part, "type");
|
|
238
|
+
|
|
239
|
+
return type === "tool-call" ||
|
|
240
|
+
(typeof type === "string" && type.startsWith("tool-") && type !== "tool-result" &&
|
|
241
|
+
type !== "tool_result");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function getToolCallShape(
|
|
245
|
+
part: Record<string, unknown>,
|
|
246
|
+
): z.infer<typeof RuntimeToolCallSchema> | null {
|
|
247
|
+
const id = getPartString(part, "toolCallId", "tool_call_id", "id");
|
|
248
|
+
const name = getPartString(part, "toolName", "tool_name", "name");
|
|
249
|
+
|
|
250
|
+
if (!id || !name) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
id,
|
|
256
|
+
type: "function",
|
|
257
|
+
function: {
|
|
258
|
+
name,
|
|
259
|
+
arguments: serializeToolArguments(extractToolArgs(part)),
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function isToolResultPart(part: Record<string, unknown>): boolean {
|
|
265
|
+
const type = getPartString(part, "type");
|
|
266
|
+
return type === "tool-result" || type === "tool_result";
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function stringifyToolResult(result: unknown): string {
|
|
270
|
+
if (typeof result === "string") {
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
return JSON.stringify(result);
|
|
276
|
+
} catch {
|
|
277
|
+
return String(result);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function toRuntimeMessage(
|
|
282
|
+
message: RuntimeMessage | InternalAgentCompatibilityMessage,
|
|
283
|
+
): RuntimeMessage {
|
|
284
|
+
if (!("parts" in message)) {
|
|
285
|
+
return message;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const textContent = message.parts
|
|
289
|
+
.filter((part) => part.type === "text" && typeof part.text === "string")
|
|
290
|
+
.map((part) => part.text)
|
|
291
|
+
.join("\n");
|
|
292
|
+
|
|
293
|
+
const sharedFields = {
|
|
294
|
+
...(message.metadata ? { metadata: message.metadata } : {}),
|
|
295
|
+
...(message.createdAt ? { createdAt: message.createdAt } : {}),
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
switch (message.role) {
|
|
299
|
+
case "system":
|
|
300
|
+
return {
|
|
301
|
+
id: message.id,
|
|
302
|
+
role: "system",
|
|
303
|
+
content: textContent,
|
|
304
|
+
...sharedFields,
|
|
305
|
+
};
|
|
306
|
+
case "user":
|
|
307
|
+
return {
|
|
308
|
+
id: message.id,
|
|
309
|
+
role: "user",
|
|
310
|
+
content: textContent,
|
|
311
|
+
...sharedFields,
|
|
312
|
+
};
|
|
313
|
+
case "assistant": {
|
|
314
|
+
const toolCalls = message.parts.flatMap((part) => {
|
|
315
|
+
if (!isCanonicalToolCallPart(part) && !isLegacyToolCallPart(part)) {
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const toolCall = getToolCallShape(part);
|
|
320
|
+
return toolCall ? [toolCall] : [];
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
id: message.id,
|
|
325
|
+
role: "assistant",
|
|
326
|
+
...(textContent ? { content: textContent } : {}),
|
|
327
|
+
...(toolCalls.length ? { toolCalls } : {}),
|
|
328
|
+
...sharedFields,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
case "tool": {
|
|
332
|
+
const toolResultPart = message.parts.find(
|
|
333
|
+
(part) =>
|
|
334
|
+
isToolResultPart(part) && getPartString(part, "toolCallId", "tool_call_id") !== null,
|
|
335
|
+
);
|
|
336
|
+
const toolCallId = toolResultPart
|
|
337
|
+
? getPartString(toolResultPart, "toolCallId", "tool_call_id")
|
|
338
|
+
: null;
|
|
339
|
+
const toolResult = toolResultPart && "result" in toolResultPart
|
|
340
|
+
? toolResultPart.result
|
|
341
|
+
: toolResultPart && "output" in toolResultPart
|
|
342
|
+
? toolResultPart.output
|
|
343
|
+
: undefined;
|
|
344
|
+
const toolError = toolResultPart ? getPartString(toolResultPart, "error") : null;
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
id: message.id,
|
|
348
|
+
role: "tool",
|
|
349
|
+
toolCallId: toolCallId ?? message.id,
|
|
350
|
+
content: toolResult !== undefined ? stringifyToolResult(toolResult) : textContent,
|
|
351
|
+
...(toolError ? { error: toolError } : {}),
|
|
352
|
+
...sharedFields,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function toRuntimeRunAgentInput(
|
|
359
|
+
input: z.infer<typeof InternalAgentStreamRequestSchema>,
|
|
360
|
+
): z.infer<typeof RuntimeRunAgentInputSchema> {
|
|
361
|
+
return {
|
|
362
|
+
threadId: input.threadId,
|
|
363
|
+
runId: input.runId,
|
|
364
|
+
...(input.parentRunId ? { parentRunId: input.parentRunId } : {}),
|
|
365
|
+
...(input.state !== undefined ? { state: input.state } : {}),
|
|
366
|
+
messages: input.messages.map(toRuntimeMessage),
|
|
367
|
+
tools: input.tools,
|
|
368
|
+
context: input.context,
|
|
369
|
+
...(input.forwardedProps ? { forwardedProps: input.forwardedProps } : {}),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
107
373
|
export const ResumeSignalSchema = z.discriminatedUnion("type", [
|
|
108
374
|
z.object({
|
|
109
375
|
type: z.literal("tool_result"),
|
|
@@ -120,4 +386,5 @@ export type RuntimeInjectedTool = z.infer<typeof RuntimeInjectedToolSchema>;
|
|
|
120
386
|
export type RuntimeContextItem = z.infer<typeof RuntimeContextItemSchema>;
|
|
121
387
|
export type RuntimeAgentSourceContext = z.infer<typeof RuntimeAgentSourceContextSchema>;
|
|
122
388
|
export type RuntimeRunAgentInput = z.infer<typeof RuntimeRunAgentInputSchema>;
|
|
389
|
+
export type InternalAgentStreamRequest = z.infer<typeof InternalAgentStreamRequestSchema>;
|
|
123
390
|
export type ResumeSignal = z.infer<typeof ResumeSignalSchema>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface FormElicitationOptions {
|
|
2
|
+
message: string;
|
|
3
|
+
schema: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface UrlElicitationOptions {
|
|
7
|
+
message: string;
|
|
8
|
+
url: string;
|
|
9
|
+
elicitationId: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ElicitationRequest {
|
|
13
|
+
method: "elicitation/create";
|
|
14
|
+
params: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function buildFormElicitation(
|
|
18
|
+
options: FormElicitationOptions,
|
|
19
|
+
): ElicitationRequest {
|
|
20
|
+
return {
|
|
21
|
+
method: "elicitation/create",
|
|
22
|
+
params: {
|
|
23
|
+
mode: "form",
|
|
24
|
+
message: options.message,
|
|
25
|
+
requestedSchema: options.schema,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildUrlElicitation(
|
|
31
|
+
options: UrlElicitationOptions,
|
|
32
|
+
): ElicitationRequest {
|
|
33
|
+
return {
|
|
34
|
+
method: "elicitation/create",
|
|
35
|
+
params: {
|
|
36
|
+
mode: "url",
|
|
37
|
+
message: options.message,
|
|
38
|
+
url: options.url,
|
|
39
|
+
elicitationId: options.elicitationId,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
package/src/src/mcp/index.ts
CHANGED
|
@@ -43,5 +43,14 @@ export {
|
|
|
43
43
|
|
|
44
44
|
export { createMCPServer, type IntegrationLoaderConfig, MCPServer } from "./server.js";
|
|
45
45
|
|
|
46
|
+
export {
|
|
47
|
+
buildFormElicitation,
|
|
48
|
+
buildUrlElicitation,
|
|
49
|
+
type ElicitationRequest,
|
|
50
|
+
type FormElicitationOptions,
|
|
51
|
+
type UrlElicitationOptions,
|
|
52
|
+
} from "./elicitation.js";
|
|
46
53
|
export { formatSSEEvent, formatSSEPrimingEvent, formatSSERetry } from "./sse.js";
|
|
47
54
|
export { SessionManager } from "./session.js";
|
|
55
|
+
export { TaskStore } from "./task-store.js";
|
|
56
|
+
export type { Task } from "./task-store.js";
|
package/src/src/mcp/server.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { VeryfrontError } from "../security/input-validation/errors.js";
|
|
|
14
14
|
import type { IntegrationRuntimeConfig } from "../integrations/types.js";
|
|
15
15
|
import { logger as baseLogger } from "../utils/index.js";
|
|
16
16
|
import { SessionManager } from "./session.js";
|
|
17
|
+
import { TaskStore } from "./task-store.js";
|
|
17
18
|
|
|
18
19
|
const logger = baseLogger.component("mcp-server");
|
|
19
20
|
|
|
@@ -110,10 +111,29 @@ export interface IntegrationLoaderConfig {
|
|
|
110
111
|
const MCP_SUPPORTED_VERSIONS = ["2025-11-25", "2024-11-05"];
|
|
111
112
|
|
|
112
113
|
export class MCPServer {
|
|
114
|
+
private static LOG_LEVELS = [
|
|
115
|
+
"debug",
|
|
116
|
+
"info",
|
|
117
|
+
"notice",
|
|
118
|
+
"warning",
|
|
119
|
+
"error",
|
|
120
|
+
"critical",
|
|
121
|
+
"alert",
|
|
122
|
+
"emergency",
|
|
123
|
+
] as const;
|
|
124
|
+
private logLevel: typeof MCPServer.LOG_LEVELS[number] = "warning";
|
|
113
125
|
private config: MCPServerConfig;
|
|
114
126
|
private integrationLoader?: IntegrationLoaderConfig;
|
|
115
127
|
private integrationsLoaded = false;
|
|
116
128
|
private sessionManager = new SessionManager();
|
|
129
|
+
private taskStore = new TaskStore();
|
|
130
|
+
private pendingTasks = new Map<string, Promise<void>>();
|
|
131
|
+
// TODO(#842): capabilities should be stored per-session (keyed by MCP-Session-Id)
|
|
132
|
+
// so concurrent clients don't overwrite each other's capability flags.
|
|
133
|
+
private clientCapabilities: Record<string, unknown> = {};
|
|
134
|
+
|
|
135
|
+
/** Callback for server-initiated notifications. Set by transport layer. */
|
|
136
|
+
onNotification?: (notification: { jsonrpc: "2.0"; method: string; params?: unknown }) => void;
|
|
117
137
|
|
|
118
138
|
constructor(config: MCPServerConfig) {
|
|
119
139
|
this.config = config;
|
|
@@ -123,6 +143,18 @@ export class MCPServer {
|
|
|
123
143
|
}
|
|
124
144
|
}
|
|
125
145
|
|
|
146
|
+
notifyToolsChanged(): void {
|
|
147
|
+
this.onNotification?.({ jsonrpc: "2.0", method: "notifications/tools/list_changed" });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
notifyResourcesChanged(): void {
|
|
151
|
+
this.onNotification?.({ jsonrpc: "2.0", method: "notifications/resources/list_changed" });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
notifyPromptsChanged(): void {
|
|
155
|
+
this.onNotification?.({ jsonrpc: "2.0", method: "notifications/prompts/list_changed" });
|
|
156
|
+
}
|
|
157
|
+
|
|
126
158
|
/**
|
|
127
159
|
* Configure integration tools to be loaded from the API.
|
|
128
160
|
*
|
|
@@ -135,6 +167,15 @@ export class MCPServer {
|
|
|
135
167
|
this.integrationsLoaded = false;
|
|
136
168
|
}
|
|
137
169
|
|
|
170
|
+
clientSupportsElicitation(mode: "form" | "url"): boolean {
|
|
171
|
+
const raw = this.clientCapabilities.elicitation;
|
|
172
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return false;
|
|
173
|
+
const elicitation = raw as Record<string, unknown>;
|
|
174
|
+
// Per MCP spec: empty elicitation object implies basic form support (backwards compat)
|
|
175
|
+
if (mode === "form" && Object.keys(elicitation).length === 0) return true;
|
|
176
|
+
return mode in elicitation;
|
|
177
|
+
}
|
|
178
|
+
|
|
138
179
|
handleRequest(request: JSONRPCRequest, context?: ToolExecutionContext): Promise<JSONRPCResponse> {
|
|
139
180
|
return withSpan(
|
|
140
181
|
"mcp.handleRequest",
|
|
@@ -178,8 +219,21 @@ export class MCPServer {
|
|
|
178
219
|
return this.initialize(params);
|
|
179
220
|
case "notifications/initialized":
|
|
180
221
|
return Promise.resolve({});
|
|
222
|
+
case "notifications/cancelled":
|
|
223
|
+
// TODO(#841): propagate cancellation to in-flight tool executions via AbortController
|
|
224
|
+
return Promise.resolve({});
|
|
181
225
|
case "completion/complete":
|
|
182
226
|
return this.complete(params);
|
|
227
|
+
case "logging/setLevel":
|
|
228
|
+
return this.setLogLevel(params);
|
|
229
|
+
case "tasks/get":
|
|
230
|
+
return this.getTask(params);
|
|
231
|
+
case "tasks/result":
|
|
232
|
+
return this.getTaskResult(params);
|
|
233
|
+
case "tasks/cancel":
|
|
234
|
+
return this.cancelTask(params);
|
|
235
|
+
case "tasks/list":
|
|
236
|
+
return this.listTasks();
|
|
183
237
|
default:
|
|
184
238
|
throw toError(
|
|
185
239
|
createError({
|
|
@@ -197,6 +251,9 @@ export class MCPServer {
|
|
|
197
251
|
? requested
|
|
198
252
|
: MCP_SUPPORTED_VERSIONS[0];
|
|
199
253
|
|
|
254
|
+
const clientCaps = (p.capabilities ?? {}) as Record<string, unknown>;
|
|
255
|
+
this.clientCapabilities = clientCaps;
|
|
256
|
+
|
|
200
257
|
return Promise.resolve({
|
|
201
258
|
protocolVersion: negotiated,
|
|
202
259
|
serverInfo: {
|
|
@@ -211,6 +268,8 @@ export class MCPServer {
|
|
|
211
268
|
resources: { subscribe: true, listChanged: true },
|
|
212
269
|
prompts: { listChanged: true },
|
|
213
270
|
completions: {},
|
|
271
|
+
logging: {},
|
|
272
|
+
tasks: { list: {}, cancel: {}, requests: { tools: { call: {} } } },
|
|
214
273
|
},
|
|
215
274
|
instructions:
|
|
216
275
|
"Veryfront MCP server provides development tools. Use vf_get_errors to check for code errors, vf_get_logs for server logs, vf_scaffold for code generation, and vf_get_project_context for project structure.",
|
|
@@ -250,7 +309,13 @@ export class MCPServer {
|
|
|
250
309
|
params: JSONRPCParams | undefined,
|
|
251
310
|
context?: ToolExecutionContext,
|
|
252
311
|
): Promise<Record<string, unknown>> {
|
|
253
|
-
const
|
|
312
|
+
const p = toParamsRecord(params);
|
|
313
|
+
const { name, arguments: args } = p;
|
|
314
|
+
const meta = (p._meta ?? {}) as Record<string, unknown>;
|
|
315
|
+
const rawToken = meta.progressToken;
|
|
316
|
+
const progressToken = (typeof rawToken === "string" || typeof rawToken === "number")
|
|
317
|
+
? rawToken
|
|
318
|
+
: undefined;
|
|
254
319
|
|
|
255
320
|
if (!name) {
|
|
256
321
|
throw toError(createError({ type: "agent", message: "Tool name is required" }));
|
|
@@ -273,11 +338,55 @@ export class MCPServer {
|
|
|
273
338
|
}
|
|
274
339
|
}
|
|
275
340
|
|
|
341
|
+
const toolContext: ToolExecutionContext | undefined = progressToken !== undefined
|
|
342
|
+
? { ...context, progressToken }
|
|
343
|
+
: context;
|
|
344
|
+
|
|
345
|
+
// Async task mode: if the caller provides a `task` field, create a task
|
|
346
|
+
// and run the tool in the background, returning the task immediately.
|
|
347
|
+
const taskParam = p.task as { ttl?: number } | undefined;
|
|
348
|
+
if (taskParam) {
|
|
349
|
+
const MIN_TTL = 1000;
|
|
350
|
+
const MAX_TTL = 3_600_000; // 1 hour
|
|
351
|
+
const rawTtl = typeof taskParam.ttl === "number" ? taskParam.ttl : 60000;
|
|
352
|
+
const ttl = Math.max(MIN_TTL, Math.min(MAX_TTL, rawTtl));
|
|
353
|
+
const task = this.taskStore.create(ttl);
|
|
354
|
+
|
|
355
|
+
// Run tool in background, update task on completion
|
|
356
|
+
// TODO(#842): wire AbortController so that tasks/cancel actually aborts the running tool execution
|
|
357
|
+
const pending = withSpan(
|
|
358
|
+
"mcp.callTool.async",
|
|
359
|
+
async () => {
|
|
360
|
+
try {
|
|
361
|
+
const result = await executeTool(toolName, args, toolContext);
|
|
362
|
+
this.taskStore.complete(task.taskId, {
|
|
363
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
364
|
+
isError: false,
|
|
365
|
+
});
|
|
366
|
+
} catch (error) {
|
|
367
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
368
|
+
logger.warn("Async tool execution failed", {
|
|
369
|
+
tool: toolName,
|
|
370
|
+
taskId: task.taskId,
|
|
371
|
+
error: message,
|
|
372
|
+
});
|
|
373
|
+
this.taskStore.fail(task.taskId, message);
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
{ "mcp.tool.name": toolName, "mcp.task.id": task.taskId },
|
|
377
|
+
).then(() => {
|
|
378
|
+
this.pendingTasks.delete(task.taskId);
|
|
379
|
+
});
|
|
380
|
+
this.pendingTasks.set(task.taskId, pending);
|
|
381
|
+
|
|
382
|
+
return Promise.resolve({ task });
|
|
383
|
+
}
|
|
384
|
+
|
|
276
385
|
return withSpan(
|
|
277
386
|
"mcp.callTool",
|
|
278
387
|
async () => {
|
|
279
388
|
try {
|
|
280
|
-
const result = await executeTool(toolName, args,
|
|
389
|
+
const result = await executeTool(toolName, args, toolContext);
|
|
281
390
|
return {
|
|
282
391
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
283
392
|
isError: false,
|
|
@@ -447,6 +556,75 @@ export class MCPServer {
|
|
|
447
556
|
});
|
|
448
557
|
}
|
|
449
558
|
|
|
559
|
+
private setLogLevel(
|
|
560
|
+
params: JSONRPCParams | undefined,
|
|
561
|
+
): Promise<Record<string, unknown>> {
|
|
562
|
+
const p = toParamsRecord(params);
|
|
563
|
+
const level = p.level as string;
|
|
564
|
+
if (
|
|
565
|
+
!MCPServer.LOG_LEVELS.includes(
|
|
566
|
+
level as typeof MCPServer.LOG_LEVELS[number],
|
|
567
|
+
)
|
|
568
|
+
) {
|
|
569
|
+
return Promise.reject({
|
|
570
|
+
code: -32602,
|
|
571
|
+
message: `Invalid log level: ${level}. Valid levels: ${MCPServer.LOG_LEVELS.join(", ")}`,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
this.logLevel = level as typeof MCPServer.LOG_LEVELS[number];
|
|
575
|
+
return Promise.resolve({});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private getTask(params: JSONRPCParams | undefined): Promise<Record<string, unknown>> {
|
|
579
|
+
const { taskId } = toParamsRecord(params);
|
|
580
|
+
if (!taskId) {
|
|
581
|
+
throw new JsonRpcError(-32602, "taskId is required");
|
|
582
|
+
}
|
|
583
|
+
const task = this.taskStore.get(String(taskId));
|
|
584
|
+
if (!task) {
|
|
585
|
+
throw new JsonRpcError(-32602, `Task not found: ${taskId}`);
|
|
586
|
+
}
|
|
587
|
+
return Promise.resolve({ ...task });
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
private getTaskResult(params: JSONRPCParams | undefined): Promise<Record<string, unknown>> {
|
|
591
|
+
const { taskId } = toParamsRecord(params);
|
|
592
|
+
if (!taskId) {
|
|
593
|
+
throw new JsonRpcError(-32602, "taskId is required");
|
|
594
|
+
}
|
|
595
|
+
const task = this.taskStore.get(String(taskId));
|
|
596
|
+
if (!task) {
|
|
597
|
+
throw new JsonRpcError(-32602, `Task not found: ${taskId}`);
|
|
598
|
+
}
|
|
599
|
+
const result = this.taskStore.getResult(String(taskId));
|
|
600
|
+
if (result === undefined) {
|
|
601
|
+
throw new JsonRpcError(-32002, "Task result is not yet available");
|
|
602
|
+
}
|
|
603
|
+
return Promise.resolve(result as Record<string, unknown>);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
private cancelTask(params: JSONRPCParams | undefined): Promise<Record<string, unknown>> {
|
|
607
|
+
const { taskId } = toParamsRecord(params);
|
|
608
|
+
if (!taskId) {
|
|
609
|
+
throw new JsonRpcError(-32602, "taskId is required");
|
|
610
|
+
}
|
|
611
|
+
const cancelled = this.taskStore.cancel(String(taskId));
|
|
612
|
+
if (!cancelled) {
|
|
613
|
+
throw new JsonRpcError(-32002, `Cannot cancel task: ${taskId}`);
|
|
614
|
+
}
|
|
615
|
+
const task = this.taskStore.get(String(taskId));
|
|
616
|
+
return Promise.resolve({ ...task });
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
private listTasks(): Promise<Record<string, unknown>> {
|
|
620
|
+
return Promise.resolve({ tasks: this.taskStore.list() });
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/** Wait for all background task executions to settle. Useful in tests. */
|
|
624
|
+
waitForPendingTasks(): Promise<void> {
|
|
625
|
+
return Promise.all(this.pendingTasks.values()).then(() => {});
|
|
626
|
+
}
|
|
627
|
+
|
|
450
628
|
createHTTPHandler(): (request: dntShim.Request) => Promise<dntShim.Response> {
|
|
451
629
|
return async (request: dntShim.Request) => {
|
|
452
630
|
const requestOrigin = request.headers.get("Origin");
|
|
@@ -623,6 +801,11 @@ export class MCPServer {
|
|
|
623
801
|
};
|
|
624
802
|
}
|
|
625
803
|
await syncIntegrationConfig(apiBaseUrl, apiToken, integrationConfigs);
|
|
804
|
+
try {
|
|
805
|
+
this.notifyToolsChanged();
|
|
806
|
+
} catch (_) {
|
|
807
|
+
// Notification delivery failure is non-fatal — sync already succeeded
|
|
808
|
+
}
|
|
626
809
|
return true;
|
|
627
810
|
}
|
|
628
811
|
}
|