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
|
@@ -61,22 +61,233 @@ export const RuntimeAgentSourceContextSchema = z.discriminatedUnion("type", [
|
|
|
61
61
|
releaseId: z.string().min(1).max(255),
|
|
62
62
|
}),
|
|
63
63
|
]);
|
|
64
|
+
const RuntimeMessageExtensionFieldsSchema = {
|
|
65
|
+
name: z.string().max(256).optional(),
|
|
66
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
67
|
+
createdAt: z.string().optional(),
|
|
68
|
+
};
|
|
69
|
+
export const RuntimeToolFunctionCallSchema = z.object({
|
|
70
|
+
name: ClientToolNameSchema,
|
|
71
|
+
arguments: z.string().max(MAX_TOOL_PARAMETERS_BYTES),
|
|
72
|
+
}).strict();
|
|
73
|
+
export const RuntimeToolCallSchema = z.object({
|
|
74
|
+
id: z.string().min(1).max(128),
|
|
75
|
+
type: z.literal("function"),
|
|
76
|
+
function: RuntimeToolFunctionCallSchema,
|
|
77
|
+
}).strict();
|
|
78
|
+
export const RuntimeSystemMessageSchema = z.object({
|
|
79
|
+
id: z.string().min(1),
|
|
80
|
+
role: z.literal("system"),
|
|
81
|
+
content: z.string(),
|
|
82
|
+
...RuntimeMessageExtensionFieldsSchema,
|
|
83
|
+
}).strict();
|
|
84
|
+
export const RuntimeUserMessageSchema = z.object({
|
|
85
|
+
id: z.string().min(1),
|
|
86
|
+
role: z.literal("user"),
|
|
87
|
+
content: z.string(),
|
|
88
|
+
...RuntimeMessageExtensionFieldsSchema,
|
|
89
|
+
}).strict();
|
|
90
|
+
export const RuntimeAssistantMessageSchema = z.object({
|
|
91
|
+
id: z.string().min(1),
|
|
92
|
+
role: z.literal("assistant"),
|
|
93
|
+
content: z.string().optional(),
|
|
94
|
+
toolCalls: z.array(RuntimeToolCallSchema).optional(),
|
|
95
|
+
...RuntimeMessageExtensionFieldsSchema,
|
|
96
|
+
}).strict();
|
|
97
|
+
export const RuntimeToolMessageSchema = z.object({
|
|
98
|
+
id: z.string().min(1),
|
|
99
|
+
role: z.literal("tool"),
|
|
100
|
+
toolCallId: z.string().min(1).max(128),
|
|
101
|
+
content: z.string(),
|
|
102
|
+
error: z.string().optional(),
|
|
103
|
+
...RuntimeMessageExtensionFieldsSchema,
|
|
104
|
+
}).strict();
|
|
105
|
+
export const RuntimeMessageSchema = z.discriminatedUnion("role", [
|
|
106
|
+
RuntimeSystemMessageSchema,
|
|
107
|
+
RuntimeUserMessageSchema,
|
|
108
|
+
RuntimeAssistantMessageSchema,
|
|
109
|
+
RuntimeToolMessageSchema,
|
|
110
|
+
]);
|
|
111
|
+
export const RuntimeContextSchema = z.union([
|
|
112
|
+
z.object({
|
|
113
|
+
description: z.string().max(1024),
|
|
114
|
+
value: z.string().max(MAX_CONTEXT_ITEM_BYTES),
|
|
115
|
+
}),
|
|
116
|
+
RuntimeContextItemSchema,
|
|
117
|
+
]);
|
|
64
118
|
export const RuntimeRunAgentInputSchema = z.object({
|
|
119
|
+
threadId: z.string().uuid(),
|
|
120
|
+
runId: RunIdSchema,
|
|
121
|
+
parentRunId: RunIdSchema.optional(),
|
|
122
|
+
state: z.unknown().optional(),
|
|
123
|
+
messages: z.array(RuntimeMessageSchema).max(MAX_RUNTIME_MESSAGES),
|
|
124
|
+
tools: z.array(RuntimeInjectedToolSchema).max(50).default([]),
|
|
125
|
+
context: z.array(RuntimeContextSchema).max(10).default([]).refine((value) => isWithinJsonSizeLimit(value, MAX_CONTEXT_TOTAL_BYTES), { message: "context must be less than 64 KB total" }),
|
|
126
|
+
forwardedProps: z.record(z.string(), z.unknown()).optional().refine((value) => value === undefined || isWithinJsonSizeLimit(value, MAX_FORWARDED_PROPS_BYTES), { message: "forwardedProps must be less than 64 KB" }),
|
|
127
|
+
});
|
|
128
|
+
export const InternalAgentCompatibilityMessageSchema = z.object({
|
|
129
|
+
id: z.string().min(1),
|
|
130
|
+
role: z.enum(["user", "assistant", "system", "tool"]),
|
|
131
|
+
parts: z.array(z.object({ type: z.string().min(1) }).passthrough()).default([]),
|
|
132
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
133
|
+
createdAt: z.string().optional(),
|
|
134
|
+
});
|
|
135
|
+
export const InternalAgentStreamRequestSchema = z.object({
|
|
65
136
|
agentId: AgentIdSchema,
|
|
66
137
|
threadId: z.string().uuid(),
|
|
67
138
|
runId: RunIdSchema,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
parts: z.array(z.object({ type: z.string().min(1) }).passthrough()).default([]),
|
|
72
|
-
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
73
|
-
createdAt: z.string().optional(),
|
|
74
|
-
})).max(MAX_RUNTIME_MESSAGES),
|
|
139
|
+
parentRunId: RunIdSchema.optional(),
|
|
140
|
+
state: z.unknown().optional(),
|
|
141
|
+
messages: z.array(z.union([RuntimeMessageSchema, InternalAgentCompatibilityMessageSchema])).max(MAX_RUNTIME_MESSAGES),
|
|
75
142
|
tools: z.array(RuntimeInjectedToolSchema).max(50).default([]),
|
|
76
|
-
context: z.array(
|
|
143
|
+
context: z.array(RuntimeContextSchema).max(10).default([]).refine((value) => isWithinJsonSizeLimit(value, MAX_CONTEXT_TOTAL_BYTES), { message: "context must be less than 64 KB total" }),
|
|
77
144
|
agentSource: RuntimeAgentSourceContextSchema.optional(),
|
|
78
145
|
forwardedProps: z.record(z.string(), z.unknown()).optional().refine((value) => value === undefined || isWithinJsonSizeLimit(value, MAX_FORWARDED_PROPS_BYTES), { message: "forwardedProps must be less than 64 KB" }),
|
|
79
146
|
});
|
|
147
|
+
function extractToolArgs(part) {
|
|
148
|
+
const args = part.args;
|
|
149
|
+
if (args && typeof args === "object" && !Array.isArray(args)) {
|
|
150
|
+
return args;
|
|
151
|
+
}
|
|
152
|
+
const input = part.input;
|
|
153
|
+
if (input && typeof input === "object" && !Array.isArray(input)) {
|
|
154
|
+
return input;
|
|
155
|
+
}
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
function serializeToolArguments(args) {
|
|
159
|
+
try {
|
|
160
|
+
return JSON.stringify(args);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return "{}";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function getPartString(part, ...keys) {
|
|
167
|
+
for (const key of keys) {
|
|
168
|
+
const value = part[key];
|
|
169
|
+
if (typeof value === "string" && value.length > 0) {
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
function isLegacyToolCallPart(part) {
|
|
176
|
+
return getPartString(part, "type") === "tool_call";
|
|
177
|
+
}
|
|
178
|
+
function isCanonicalToolCallPart(part) {
|
|
179
|
+
const type = getPartString(part, "type");
|
|
180
|
+
return type === "tool-call" ||
|
|
181
|
+
(typeof type === "string" && type.startsWith("tool-") && type !== "tool-result" &&
|
|
182
|
+
type !== "tool_result");
|
|
183
|
+
}
|
|
184
|
+
function getToolCallShape(part) {
|
|
185
|
+
const id = getPartString(part, "toolCallId", "tool_call_id", "id");
|
|
186
|
+
const name = getPartString(part, "toolName", "tool_name", "name");
|
|
187
|
+
if (!id || !name) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
id,
|
|
192
|
+
type: "function",
|
|
193
|
+
function: {
|
|
194
|
+
name,
|
|
195
|
+
arguments: serializeToolArguments(extractToolArgs(part)),
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function isToolResultPart(part) {
|
|
200
|
+
const type = getPartString(part, "type");
|
|
201
|
+
return type === "tool-result" || type === "tool_result";
|
|
202
|
+
}
|
|
203
|
+
function stringifyToolResult(result) {
|
|
204
|
+
if (typeof result === "string") {
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
return JSON.stringify(result);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return String(result);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function toRuntimeMessage(message) {
|
|
215
|
+
if (!("parts" in message)) {
|
|
216
|
+
return message;
|
|
217
|
+
}
|
|
218
|
+
const textContent = message.parts
|
|
219
|
+
.filter((part) => part.type === "text" && typeof part.text === "string")
|
|
220
|
+
.map((part) => part.text)
|
|
221
|
+
.join("\n");
|
|
222
|
+
const sharedFields = {
|
|
223
|
+
...(message.metadata ? { metadata: message.metadata } : {}),
|
|
224
|
+
...(message.createdAt ? { createdAt: message.createdAt } : {}),
|
|
225
|
+
};
|
|
226
|
+
switch (message.role) {
|
|
227
|
+
case "system":
|
|
228
|
+
return {
|
|
229
|
+
id: message.id,
|
|
230
|
+
role: "system",
|
|
231
|
+
content: textContent,
|
|
232
|
+
...sharedFields,
|
|
233
|
+
};
|
|
234
|
+
case "user":
|
|
235
|
+
return {
|
|
236
|
+
id: message.id,
|
|
237
|
+
role: "user",
|
|
238
|
+
content: textContent,
|
|
239
|
+
...sharedFields,
|
|
240
|
+
};
|
|
241
|
+
case "assistant": {
|
|
242
|
+
const toolCalls = message.parts.flatMap((part) => {
|
|
243
|
+
if (!isCanonicalToolCallPart(part) && !isLegacyToolCallPart(part)) {
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
const toolCall = getToolCallShape(part);
|
|
247
|
+
return toolCall ? [toolCall] : [];
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
id: message.id,
|
|
251
|
+
role: "assistant",
|
|
252
|
+
...(textContent ? { content: textContent } : {}),
|
|
253
|
+
...(toolCalls.length ? { toolCalls } : {}),
|
|
254
|
+
...sharedFields,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
case "tool": {
|
|
258
|
+
const toolResultPart = message.parts.find((part) => isToolResultPart(part) && getPartString(part, "toolCallId", "tool_call_id") !== null);
|
|
259
|
+
const toolCallId = toolResultPart
|
|
260
|
+
? getPartString(toolResultPart, "toolCallId", "tool_call_id")
|
|
261
|
+
: null;
|
|
262
|
+
const toolResult = toolResultPart && "result" in toolResultPart
|
|
263
|
+
? toolResultPart.result
|
|
264
|
+
: toolResultPart && "output" in toolResultPart
|
|
265
|
+
? toolResultPart.output
|
|
266
|
+
: undefined;
|
|
267
|
+
const toolError = toolResultPart ? getPartString(toolResultPart, "error") : null;
|
|
268
|
+
return {
|
|
269
|
+
id: message.id,
|
|
270
|
+
role: "tool",
|
|
271
|
+
toolCallId: toolCallId ?? message.id,
|
|
272
|
+
content: toolResult !== undefined ? stringifyToolResult(toolResult) : textContent,
|
|
273
|
+
...(toolError ? { error: toolError } : {}),
|
|
274
|
+
...sharedFields,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
export function toRuntimeRunAgentInput(input) {
|
|
280
|
+
return {
|
|
281
|
+
threadId: input.threadId,
|
|
282
|
+
runId: input.runId,
|
|
283
|
+
...(input.parentRunId ? { parentRunId: input.parentRunId } : {}),
|
|
284
|
+
...(input.state !== undefined ? { state: input.state } : {}),
|
|
285
|
+
messages: input.messages.map(toRuntimeMessage),
|
|
286
|
+
tools: input.tools,
|
|
287
|
+
context: input.context,
|
|
288
|
+
...(input.forwardedProps ? { forwardedProps: input.forwardedProps } : {}),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
80
291
|
export const ResumeSignalSchema = z.discriminatedUnion("type", [
|
|
81
292
|
z.object({
|
|
82
293
|
type: z.literal("tool_result"),
|
|
@@ -2,9 +2,9 @@ import { z } from "zod";
|
|
|
2
2
|
export declare const JobStatusSchema: z.ZodEnum<{
|
|
3
3
|
failed: "failed";
|
|
4
4
|
completed: "completed";
|
|
5
|
+
working: "working";
|
|
5
6
|
canceled: "canceled";
|
|
6
7
|
submitted: "submitted";
|
|
7
|
-
working: "working";
|
|
8
8
|
}>;
|
|
9
9
|
export declare const CronJobStatusSchema: z.ZodEnum<{
|
|
10
10
|
deleting: "deleting";
|
|
@@ -199,9 +199,9 @@ export declare const JobSchema: z.ZodObject<{
|
|
|
199
199
|
status: z.ZodEnum<{
|
|
200
200
|
failed: "failed";
|
|
201
201
|
completed: "completed";
|
|
202
|
+
working: "working";
|
|
202
203
|
canceled: "canceled";
|
|
203
204
|
submitted: "submitted";
|
|
204
|
-
working: "working";
|
|
205
205
|
}>;
|
|
206
206
|
target: z.ZodString;
|
|
207
207
|
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -302,9 +302,9 @@ export declare const JobListItemSchema: z.ZodObject<{
|
|
|
302
302
|
status: z.ZodEnum<{
|
|
303
303
|
failed: "failed";
|
|
304
304
|
completed: "completed";
|
|
305
|
+
working: "working";
|
|
305
306
|
canceled: "canceled";
|
|
306
307
|
submitted: "submitted";
|
|
307
|
-
working: "working";
|
|
308
308
|
}>;
|
|
309
309
|
target: z.ZodString;
|
|
310
310
|
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -351,9 +351,9 @@ export declare const PaginatedJobsResponseSchema: z.ZodObject<{
|
|
|
351
351
|
status: z.ZodEnum<{
|
|
352
352
|
failed: "failed";
|
|
353
353
|
completed: "completed";
|
|
354
|
+
working: "working";
|
|
354
355
|
canceled: "canceled";
|
|
355
356
|
submitted: "submitted";
|
|
356
|
-
working: "working";
|
|
357
357
|
}>;
|
|
358
358
|
target: z.ZodString;
|
|
359
359
|
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface FormElicitationOptions {
|
|
2
|
+
message: string;
|
|
3
|
+
schema: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
export interface UrlElicitationOptions {
|
|
6
|
+
message: string;
|
|
7
|
+
url: string;
|
|
8
|
+
elicitationId: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ElicitationRequest {
|
|
11
|
+
method: "elicitation/create";
|
|
12
|
+
params: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export declare function buildFormElicitation(options: FormElicitationOptions): ElicitationRequest;
|
|
15
|
+
export declare function buildUrlElicitation(options: UrlElicitationOptions): ElicitationRequest;
|
|
16
|
+
//# sourceMappingURL=elicitation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elicitation.d.ts","sourceRoot":"","sources":["../../../src/src/mcp/elicitation.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,sBAAsB,GAC9B,kBAAkB,CASpB;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,qBAAqB,GAC7B,kBAAkB,CAUpB"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function buildFormElicitation(options) {
|
|
2
|
+
return {
|
|
3
|
+
method: "elicitation/create",
|
|
4
|
+
params: {
|
|
5
|
+
mode: "form",
|
|
6
|
+
message: options.message,
|
|
7
|
+
requestedSchema: options.schema,
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function buildUrlElicitation(options) {
|
|
12
|
+
return {
|
|
13
|
+
method: "elicitation/create",
|
|
14
|
+
params: {
|
|
15
|
+
mode: "url",
|
|
16
|
+
message: options.message,
|
|
17
|
+
url: options.url,
|
|
18
|
+
elicitationId: options.elicitationId,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
package/esm/src/mcp/index.d.ts
CHANGED
|
@@ -25,6 +25,9 @@ import "../../_dnt.polyfills.js";
|
|
|
25
25
|
export type { MCPServerConfig, MCPStats, MCPTool, ToolAnnotations, ToolListEntry, } from "./types.js";
|
|
26
26
|
export { clearMCPRegistry, getMCPRegistry, getMCPStats, registerPrompt, registerResource, registerTool, } from "./registry.js";
|
|
27
27
|
export { createMCPServer, type IntegrationLoaderConfig, MCPServer } from "./server.js";
|
|
28
|
+
export { buildFormElicitation, buildUrlElicitation, type ElicitationRequest, type FormElicitationOptions, type UrlElicitationOptions, } from "./elicitation.js";
|
|
28
29
|
export { formatSSEEvent, formatSSEPrimingEvent, formatSSERetry } from "./sse.js";
|
|
29
30
|
export { SessionManager } from "./session.js";
|
|
31
|
+
export { TaskStore } from "./task-store.js";
|
|
32
|
+
export type { Task } from "./task-store.js";
|
|
30
33
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,yBAAyB,CAAC;AAGjC,YAAY,EACV,eAAe,EACf,QAAQ,EACR,OAAO,EACP,eAAe,EACf,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,YAAY,GACb,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,eAAe,EAAE,KAAK,uBAAuB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEvF,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,yBAAyB,CAAC;AAGjC,YAAY,EACV,eAAe,EACf,QAAQ,EACR,OAAO,EACP,eAAe,EACf,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,YAAY,GACb,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,eAAe,EAAE,KAAK,uBAAuB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEvF,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC"}
|
package/esm/src/mcp/index.js
CHANGED
|
@@ -24,5 +24,7 @@
|
|
|
24
24
|
import "../../_dnt.polyfills.js";
|
|
25
25
|
export { clearMCPRegistry, getMCPRegistry, getMCPStats, registerPrompt, registerResource, registerTool, } from "./registry.js";
|
|
26
26
|
export { createMCPServer, MCPServer } from "./server.js";
|
|
27
|
+
export { buildFormElicitation, buildUrlElicitation, } from "./elicitation.js";
|
|
27
28
|
export { formatSSEEvent, formatSSEPrimingEvent, formatSSERetry } from "./sse.js";
|
|
28
29
|
export { SessionManager } from "./session.js";
|
|
30
|
+
export { TaskStore } from "./task-store.js";
|
package/esm/src/mcp/server.d.ts
CHANGED
|
@@ -25,11 +25,25 @@ export interface IntegrationLoaderConfig {
|
|
|
25
25
|
apiToken?: string;
|
|
26
26
|
}
|
|
27
27
|
export declare class MCPServer {
|
|
28
|
+
private static LOG_LEVELS;
|
|
29
|
+
private logLevel;
|
|
28
30
|
private config;
|
|
29
31
|
private integrationLoader?;
|
|
30
32
|
private integrationsLoaded;
|
|
31
33
|
private sessionManager;
|
|
34
|
+
private taskStore;
|
|
35
|
+
private pendingTasks;
|
|
36
|
+
private clientCapabilities;
|
|
37
|
+
/** Callback for server-initiated notifications. Set by transport layer. */
|
|
38
|
+
onNotification?: (notification: {
|
|
39
|
+
jsonrpc: "2.0";
|
|
40
|
+
method: string;
|
|
41
|
+
params?: unknown;
|
|
42
|
+
}) => void;
|
|
32
43
|
constructor(config: MCPServerConfig);
|
|
44
|
+
notifyToolsChanged(): void;
|
|
45
|
+
notifyResourcesChanged(): void;
|
|
46
|
+
notifyPromptsChanged(): void;
|
|
33
47
|
/**
|
|
34
48
|
* Configure integration tools to be loaded from the API.
|
|
35
49
|
*
|
|
@@ -38,6 +52,7 @@ export declare class MCPServer {
|
|
|
38
52
|
* Otherwise falls back to the legacy local loading path.
|
|
39
53
|
*/
|
|
40
54
|
setIntegrationLoader(config: IntegrationLoaderConfig): void;
|
|
55
|
+
clientSupportsElicitation(mode: "form" | "url"): boolean;
|
|
41
56
|
handleRequest(request: JSONRPCRequest, context?: ToolExecutionContext): Promise<JSONRPCResponse>;
|
|
42
57
|
private dispatch;
|
|
43
58
|
private initialize;
|
|
@@ -49,6 +64,13 @@ export declare class MCPServer {
|
|
|
49
64
|
private listPrompts;
|
|
50
65
|
private getPrompt;
|
|
51
66
|
private complete;
|
|
67
|
+
private setLogLevel;
|
|
68
|
+
private getTask;
|
|
69
|
+
private getTaskResult;
|
|
70
|
+
private cancelTask;
|
|
71
|
+
private listTasks;
|
|
72
|
+
/** Wait for all background task executions to settle. Useful in tests. */
|
|
73
|
+
waitForPendingTasks(): Promise<void>;
|
|
52
74
|
createHTTPHandler(): (request: dntShim.Request) => Promise<dntShim.Response>;
|
|
53
75
|
private extractRequestContext;
|
|
54
76
|
private validateAuth;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/src/mcp/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAG/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,YAAY,CAAC;AAMjE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/src/mcp/server.ts"],"names":[],"mappings":"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"}
|
package/esm/src/mcp/server.js
CHANGED
|
@@ -11,6 +11,7 @@ import { validateContentType } from "../security/input-validation/limits.js";
|
|
|
11
11
|
import { VeryfrontError } from "../security/input-validation/errors.js";
|
|
12
12
|
import { logger as baseLogger } from "../utils/index.js";
|
|
13
13
|
import { SessionManager } from "./session.js";
|
|
14
|
+
import { TaskStore } from "./task-store.js";
|
|
14
15
|
const logger = baseLogger.component("mcp-server");
|
|
15
16
|
const MAX_REQUEST_BODY_SIZE = 1_048_576; // 1 MB
|
|
16
17
|
const MAX_CONTEXT_HEADER_LENGTH = 255;
|
|
@@ -66,16 +67,43 @@ function readAllowedHeader(request, headerName, pattern) {
|
|
|
66
67
|
}
|
|
67
68
|
const MCP_SUPPORTED_VERSIONS = ["2025-11-25", "2024-11-05"];
|
|
68
69
|
export class MCPServer {
|
|
70
|
+
static LOG_LEVELS = [
|
|
71
|
+
"debug",
|
|
72
|
+
"info",
|
|
73
|
+
"notice",
|
|
74
|
+
"warning",
|
|
75
|
+
"error",
|
|
76
|
+
"critical",
|
|
77
|
+
"alert",
|
|
78
|
+
"emergency",
|
|
79
|
+
];
|
|
80
|
+
logLevel = "warning";
|
|
69
81
|
config;
|
|
70
82
|
integrationLoader;
|
|
71
83
|
integrationsLoaded = false;
|
|
72
84
|
sessionManager = new SessionManager();
|
|
85
|
+
taskStore = new TaskStore();
|
|
86
|
+
pendingTasks = new Map();
|
|
87
|
+
// TODO(#842): capabilities should be stored per-session (keyed by MCP-Session-Id)
|
|
88
|
+
// so concurrent clients don't overwrite each other's capability flags.
|
|
89
|
+
clientCapabilities = {};
|
|
90
|
+
/** Callback for server-initiated notifications. Set by transport layer. */
|
|
91
|
+
onNotification;
|
|
73
92
|
constructor(config) {
|
|
74
93
|
this.config = config;
|
|
75
94
|
if (!config.auth || config.auth.type === "none") {
|
|
76
95
|
logger.warn("MCP server has no authentication configured — all requests will be accepted");
|
|
77
96
|
}
|
|
78
97
|
}
|
|
98
|
+
notifyToolsChanged() {
|
|
99
|
+
this.onNotification?.({ jsonrpc: "2.0", method: "notifications/tools/list_changed" });
|
|
100
|
+
}
|
|
101
|
+
notifyResourcesChanged() {
|
|
102
|
+
this.onNotification?.({ jsonrpc: "2.0", method: "notifications/resources/list_changed" });
|
|
103
|
+
}
|
|
104
|
+
notifyPromptsChanged() {
|
|
105
|
+
this.onNotification?.({ jsonrpc: "2.0", method: "notifications/prompts/list_changed" });
|
|
106
|
+
}
|
|
79
107
|
/**
|
|
80
108
|
* Configure integration tools to be loaded from the API.
|
|
81
109
|
*
|
|
@@ -87,6 +115,16 @@ export class MCPServer {
|
|
|
87
115
|
this.integrationLoader = config;
|
|
88
116
|
this.integrationsLoaded = false;
|
|
89
117
|
}
|
|
118
|
+
clientSupportsElicitation(mode) {
|
|
119
|
+
const raw = this.clientCapabilities.elicitation;
|
|
120
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
121
|
+
return false;
|
|
122
|
+
const elicitation = raw;
|
|
123
|
+
// Per MCP spec: empty elicitation object implies basic form support (backwards compat)
|
|
124
|
+
if (mode === "form" && Object.keys(elicitation).length === 0)
|
|
125
|
+
return true;
|
|
126
|
+
return mode in elicitation;
|
|
127
|
+
}
|
|
90
128
|
handleRequest(request, context) {
|
|
91
129
|
return withSpan("mcp.handleRequest", async () => {
|
|
92
130
|
try {
|
|
@@ -122,8 +160,21 @@ export class MCPServer {
|
|
|
122
160
|
return this.initialize(params);
|
|
123
161
|
case "notifications/initialized":
|
|
124
162
|
return Promise.resolve({});
|
|
163
|
+
case "notifications/cancelled":
|
|
164
|
+
// TODO(#841): propagate cancellation to in-flight tool executions via AbortController
|
|
165
|
+
return Promise.resolve({});
|
|
125
166
|
case "completion/complete":
|
|
126
167
|
return this.complete(params);
|
|
168
|
+
case "logging/setLevel":
|
|
169
|
+
return this.setLogLevel(params);
|
|
170
|
+
case "tasks/get":
|
|
171
|
+
return this.getTask(params);
|
|
172
|
+
case "tasks/result":
|
|
173
|
+
return this.getTaskResult(params);
|
|
174
|
+
case "tasks/cancel":
|
|
175
|
+
return this.cancelTask(params);
|
|
176
|
+
case "tasks/list":
|
|
177
|
+
return this.listTasks();
|
|
127
178
|
default:
|
|
128
179
|
throw toError(createError({
|
|
129
180
|
type: "agent",
|
|
@@ -137,6 +188,8 @@ export class MCPServer {
|
|
|
137
188
|
const negotiated = requested && MCP_SUPPORTED_VERSIONS.includes(requested)
|
|
138
189
|
? requested
|
|
139
190
|
: MCP_SUPPORTED_VERSIONS[0];
|
|
191
|
+
const clientCaps = (p.capabilities ?? {});
|
|
192
|
+
this.clientCapabilities = clientCaps;
|
|
140
193
|
return Promise.resolve({
|
|
141
194
|
protocolVersion: negotiated,
|
|
142
195
|
serverInfo: {
|
|
@@ -150,6 +203,8 @@ export class MCPServer {
|
|
|
150
203
|
resources: { subscribe: true, listChanged: true },
|
|
151
204
|
prompts: { listChanged: true },
|
|
152
205
|
completions: {},
|
|
206
|
+
logging: {},
|
|
207
|
+
tasks: { list: {}, cancel: {}, requests: { tools: { call: {} } } },
|
|
153
208
|
},
|
|
154
209
|
instructions: "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.",
|
|
155
210
|
});
|
|
@@ -183,7 +238,13 @@ export class MCPServer {
|
|
|
183
238
|
return { tools };
|
|
184
239
|
}
|
|
185
240
|
callTool(params, context) {
|
|
186
|
-
const
|
|
241
|
+
const p = toParamsRecord(params);
|
|
242
|
+
const { name, arguments: args } = p;
|
|
243
|
+
const meta = (p._meta ?? {});
|
|
244
|
+
const rawToken = meta.progressToken;
|
|
245
|
+
const progressToken = (typeof rawToken === "string" || typeof rawToken === "number")
|
|
246
|
+
? rawToken
|
|
247
|
+
: undefined;
|
|
187
248
|
if (!name) {
|
|
188
249
|
throw toError(createError({ type: "agent", message: "Tool name is required" }));
|
|
189
250
|
}
|
|
@@ -202,9 +263,46 @@ export class MCPServer {
|
|
|
202
263
|
throw new JsonRpcError(-32602, `Invalid arguments for tool ${toolName}: ${message}`);
|
|
203
264
|
}
|
|
204
265
|
}
|
|
266
|
+
const toolContext = progressToken !== undefined
|
|
267
|
+
? { ...context, progressToken }
|
|
268
|
+
: context;
|
|
269
|
+
// Async task mode: if the caller provides a `task` field, create a task
|
|
270
|
+
// and run the tool in the background, returning the task immediately.
|
|
271
|
+
const taskParam = p.task;
|
|
272
|
+
if (taskParam) {
|
|
273
|
+
const MIN_TTL = 1000;
|
|
274
|
+
const MAX_TTL = 3_600_000; // 1 hour
|
|
275
|
+
const rawTtl = typeof taskParam.ttl === "number" ? taskParam.ttl : 60000;
|
|
276
|
+
const ttl = Math.max(MIN_TTL, Math.min(MAX_TTL, rawTtl));
|
|
277
|
+
const task = this.taskStore.create(ttl);
|
|
278
|
+
// Run tool in background, update task on completion
|
|
279
|
+
// TODO(#842): wire AbortController so that tasks/cancel actually aborts the running tool execution
|
|
280
|
+
const pending = withSpan("mcp.callTool.async", async () => {
|
|
281
|
+
try {
|
|
282
|
+
const result = await executeTool(toolName, args, toolContext);
|
|
283
|
+
this.taskStore.complete(task.taskId, {
|
|
284
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
285
|
+
isError: false,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
290
|
+
logger.warn("Async tool execution failed", {
|
|
291
|
+
tool: toolName,
|
|
292
|
+
taskId: task.taskId,
|
|
293
|
+
error: message,
|
|
294
|
+
});
|
|
295
|
+
this.taskStore.fail(task.taskId, message);
|
|
296
|
+
}
|
|
297
|
+
}, { "mcp.tool.name": toolName, "mcp.task.id": task.taskId }).then(() => {
|
|
298
|
+
this.pendingTasks.delete(task.taskId);
|
|
299
|
+
});
|
|
300
|
+
this.pendingTasks.set(task.taskId, pending);
|
|
301
|
+
return Promise.resolve({ task });
|
|
302
|
+
}
|
|
205
303
|
return withSpan("mcp.callTool", async () => {
|
|
206
304
|
try {
|
|
207
|
-
const result = await executeTool(toolName, args,
|
|
305
|
+
const result = await executeTool(toolName, args, toolContext);
|
|
208
306
|
return {
|
|
209
307
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
210
308
|
isError: false,
|
|
@@ -327,6 +425,63 @@ export class MCPServer {
|
|
|
327
425
|
completion: { values: [], total: 0, hasMore: false },
|
|
328
426
|
});
|
|
329
427
|
}
|
|
428
|
+
setLogLevel(params) {
|
|
429
|
+
const p = toParamsRecord(params);
|
|
430
|
+
const level = p.level;
|
|
431
|
+
if (!MCPServer.LOG_LEVELS.includes(level)) {
|
|
432
|
+
return Promise.reject({
|
|
433
|
+
code: -32602,
|
|
434
|
+
message: `Invalid log level: ${level}. Valid levels: ${MCPServer.LOG_LEVELS.join(", ")}`,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
this.logLevel = level;
|
|
438
|
+
return Promise.resolve({});
|
|
439
|
+
}
|
|
440
|
+
getTask(params) {
|
|
441
|
+
const { taskId } = toParamsRecord(params);
|
|
442
|
+
if (!taskId) {
|
|
443
|
+
throw new JsonRpcError(-32602, "taskId is required");
|
|
444
|
+
}
|
|
445
|
+
const task = this.taskStore.get(String(taskId));
|
|
446
|
+
if (!task) {
|
|
447
|
+
throw new JsonRpcError(-32602, `Task not found: ${taskId}`);
|
|
448
|
+
}
|
|
449
|
+
return Promise.resolve({ ...task });
|
|
450
|
+
}
|
|
451
|
+
getTaskResult(params) {
|
|
452
|
+
const { taskId } = toParamsRecord(params);
|
|
453
|
+
if (!taskId) {
|
|
454
|
+
throw new JsonRpcError(-32602, "taskId is required");
|
|
455
|
+
}
|
|
456
|
+
const task = this.taskStore.get(String(taskId));
|
|
457
|
+
if (!task) {
|
|
458
|
+
throw new JsonRpcError(-32602, `Task not found: ${taskId}`);
|
|
459
|
+
}
|
|
460
|
+
const result = this.taskStore.getResult(String(taskId));
|
|
461
|
+
if (result === undefined) {
|
|
462
|
+
throw new JsonRpcError(-32002, "Task result is not yet available");
|
|
463
|
+
}
|
|
464
|
+
return Promise.resolve(result);
|
|
465
|
+
}
|
|
466
|
+
cancelTask(params) {
|
|
467
|
+
const { taskId } = toParamsRecord(params);
|
|
468
|
+
if (!taskId) {
|
|
469
|
+
throw new JsonRpcError(-32602, "taskId is required");
|
|
470
|
+
}
|
|
471
|
+
const cancelled = this.taskStore.cancel(String(taskId));
|
|
472
|
+
if (!cancelled) {
|
|
473
|
+
throw new JsonRpcError(-32002, `Cannot cancel task: ${taskId}`);
|
|
474
|
+
}
|
|
475
|
+
const task = this.taskStore.get(String(taskId));
|
|
476
|
+
return Promise.resolve({ ...task });
|
|
477
|
+
}
|
|
478
|
+
listTasks() {
|
|
479
|
+
return Promise.resolve({ tasks: this.taskStore.list() });
|
|
480
|
+
}
|
|
481
|
+
/** Wait for all background task executions to settle. Useful in tests. */
|
|
482
|
+
waitForPendingTasks() {
|
|
483
|
+
return Promise.all(this.pendingTasks.values()).then(() => { });
|
|
484
|
+
}
|
|
330
485
|
createHTTPHandler() {
|
|
331
486
|
return async (request) => {
|
|
332
487
|
const requestOrigin = request.headers.get("Origin");
|
|
@@ -480,6 +635,12 @@ export class MCPServer {
|
|
|
480
635
|
};
|
|
481
636
|
}
|
|
482
637
|
await syncIntegrationConfig(apiBaseUrl, apiToken, integrationConfigs);
|
|
638
|
+
try {
|
|
639
|
+
this.notifyToolsChanged();
|
|
640
|
+
}
|
|
641
|
+
catch (_) {
|
|
642
|
+
// Notification delivery failure is non-fatal — sync already succeeded
|
|
643
|
+
}
|
|
483
644
|
return true;
|
|
484
645
|
}
|
|
485
646
|
}
|