feat(planning): grille hebdomadaire complète avec API et filtres
- Connexion API via proxy Angular (résolution CORS, base path /api) - Import CSS ng-zorro global pour les modales et composants - Filtres Camion/Show câblés sur l'affichage de la grille - Camions affichés via TrucksService (linkés au show du même créneau) - Panneau de détails : spectacles + camions du jour sélectionné - Modale de création de spectacle stylisée avec fond et centrage - Positionnement précis des events à la minute dans leur créneau - Auto-scroll vers l'heure courante au chargement - Ligne "maintenant" sur la colonne du jour actuel - Régénération des services OpenAPI (nouveaux noms de types) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+72
-406
@@ -1,77 +1,12 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Client = void 0;
|
||||
exports.getSupportedElicitationModes = getSupportedElicitationModes;
|
||||
const protocol_js_1 = require("../shared/protocol.js");
|
||||
const types_js_1 = require("../types.js");
|
||||
const ajv_provider_js_1 = require("../validation/ajv-provider.js");
|
||||
const zod_compat_js_1 = require("../server/zod-compat.js");
|
||||
const client_js_1 = require("../experimental/tasks/client.js");
|
||||
const helpers_js_1 = require("../experimental/tasks/helpers.js");
|
||||
/**
|
||||
* Elicitation default application helper. Applies defaults to the data based on the schema.
|
||||
*
|
||||
* @param schema - The schema to apply defaults to.
|
||||
* @param data - The data to apply defaults to.
|
||||
*/
|
||||
function applyElicitationDefaults(schema, data) {
|
||||
if (!schema || data === null || typeof data !== 'object')
|
||||
return;
|
||||
// Handle object properties
|
||||
if (schema.type === 'object' && schema.properties && typeof schema.properties === 'object') {
|
||||
const obj = data;
|
||||
const props = schema.properties;
|
||||
for (const key of Object.keys(props)) {
|
||||
const propSchema = props[key];
|
||||
// If missing or explicitly undefined, apply default if present
|
||||
if (obj[key] === undefined && Object.prototype.hasOwnProperty.call(propSchema, 'default')) {
|
||||
obj[key] = propSchema.default;
|
||||
}
|
||||
// Recurse into existing nested objects/arrays
|
||||
if (obj[key] !== undefined) {
|
||||
applyElicitationDefaults(propSchema, obj[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(schema.anyOf)) {
|
||||
for (const sub of schema.anyOf) {
|
||||
// Skip boolean schemas (true/false are valid JSON Schemas but have no defaults)
|
||||
if (typeof sub !== 'boolean') {
|
||||
applyElicitationDefaults(sub, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Combine schemas
|
||||
if (Array.isArray(schema.oneOf)) {
|
||||
for (const sub of schema.oneOf) {
|
||||
// Skip boolean schemas (true/false are valid JSON Schemas but have no defaults)
|
||||
if (typeof sub !== 'boolean') {
|
||||
applyElicitationDefaults(sub, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Determines which elicitation modes are supported based on declared client capabilities.
|
||||
*
|
||||
* According to the spec:
|
||||
* - An empty elicitation capability object defaults to form mode support (backwards compatibility)
|
||||
* - URL mode is only supported if explicitly declared
|
||||
*
|
||||
* @param capabilities - The client's elicitation capabilities
|
||||
* @returns An object indicating which modes are supported
|
||||
*/
|
||||
function getSupportedElicitationModes(capabilities) {
|
||||
if (!capabilities) {
|
||||
return { supportsFormMode: false, supportsUrlMode: false };
|
||||
}
|
||||
const hasFormCapability = capabilities.form !== undefined;
|
||||
const hasUrlCapability = capabilities.url !== undefined;
|
||||
// If neither form nor url are explicitly declared, form mode is supported (backwards compatibility)
|
||||
const supportsFormMode = hasFormCapability || (!hasFormCapability && !hasUrlCapability);
|
||||
const supportsUrlMode = hasUrlCapability;
|
||||
return { supportsFormMode, supportsUrlMode };
|
||||
}
|
||||
const ajv_1 = __importDefault(require("ajv"));
|
||||
/**
|
||||
* An MCP client on top of a pluggable transport.
|
||||
*
|
||||
@@ -102,59 +37,12 @@ class Client extends protocol_js_1.Protocol {
|
||||
* Initializes this client with the given name and version information.
|
||||
*/
|
||||
constructor(_clientInfo, options) {
|
||||
var _a;
|
||||
super(options);
|
||||
this._clientInfo = _clientInfo;
|
||||
this._cachedToolOutputValidators = new Map();
|
||||
this._cachedKnownTaskTools = new Set();
|
||||
this._cachedRequiredTaskTools = new Set();
|
||||
this._listChangedDebounceTimers = new Map();
|
||||
this._capabilities = options?.capabilities ?? {};
|
||||
this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new ajv_provider_js_1.AjvJsonSchemaValidator();
|
||||
// Store list changed config for setup after connection (when we know server capabilities)
|
||||
if (options?.listChanged) {
|
||||
this._pendingListChangedConfig = options.listChanged;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set up handlers for list changed notifications based on config and server capabilities.
|
||||
* This should only be called after initialization when server capabilities are known.
|
||||
* Handlers are silently skipped if the server doesn't advertise the corresponding listChanged capability.
|
||||
* @internal
|
||||
*/
|
||||
_setupListChangedHandlers(config) {
|
||||
if (config.tools && this._serverCapabilities?.tools?.listChanged) {
|
||||
this._setupListChangedHandler('tools', types_js_1.ToolListChangedNotificationSchema, config.tools, async () => {
|
||||
const result = await this.listTools();
|
||||
return result.tools;
|
||||
});
|
||||
}
|
||||
if (config.prompts && this._serverCapabilities?.prompts?.listChanged) {
|
||||
this._setupListChangedHandler('prompts', types_js_1.PromptListChangedNotificationSchema, config.prompts, async () => {
|
||||
const result = await this.listPrompts();
|
||||
return result.prompts;
|
||||
});
|
||||
}
|
||||
if (config.resources && this._serverCapabilities?.resources?.listChanged) {
|
||||
this._setupListChangedHandler('resources', types_js_1.ResourceListChangedNotificationSchema, config.resources, async () => {
|
||||
const result = await this.listResources();
|
||||
return result.resources;
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Access experimental features.
|
||||
*
|
||||
* WARNING: These APIs are experimental and may change without notice.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
get experimental() {
|
||||
if (!this._experimental) {
|
||||
this._experimental = {
|
||||
tasks: new client_js_1.ExperimentalClientTasks(this)
|
||||
};
|
||||
}
|
||||
return this._experimental;
|
||||
this._capabilities = (_a = options === null || options === void 0 ? void 0 : options.capabilities) !== null && _a !== void 0 ? _a : {};
|
||||
this._ajv = new ajv_1.default();
|
||||
}
|
||||
/**
|
||||
* Registers new capabilities. This can only be called before connecting to a transport.
|
||||
@@ -163,126 +51,13 @@ class Client extends protocol_js_1.Protocol {
|
||||
*/
|
||||
registerCapabilities(capabilities) {
|
||||
if (this.transport) {
|
||||
throw new Error('Cannot register capabilities after connecting to transport');
|
||||
throw new Error("Cannot register capabilities after connecting to transport");
|
||||
}
|
||||
this._capabilities = (0, protocol_js_1.mergeCapabilities)(this._capabilities, capabilities);
|
||||
}
|
||||
/**
|
||||
* Override request handler registration to enforce client-side validation for elicitation.
|
||||
*/
|
||||
setRequestHandler(requestSchema, handler) {
|
||||
const shape = (0, zod_compat_js_1.getObjectShape)(requestSchema);
|
||||
const methodSchema = shape?.method;
|
||||
if (!methodSchema) {
|
||||
throw new Error('Schema is missing a method literal');
|
||||
}
|
||||
// Extract literal value using type-safe property access
|
||||
let methodValue;
|
||||
if ((0, zod_compat_js_1.isZ4Schema)(methodSchema)) {
|
||||
const v4Schema = methodSchema;
|
||||
const v4Def = v4Schema._zod?.def;
|
||||
methodValue = v4Def?.value ?? v4Schema.value;
|
||||
}
|
||||
else {
|
||||
const v3Schema = methodSchema;
|
||||
const legacyDef = v3Schema._def;
|
||||
methodValue = legacyDef?.value ?? v3Schema.value;
|
||||
}
|
||||
if (typeof methodValue !== 'string') {
|
||||
throw new Error('Schema method literal must be a string');
|
||||
}
|
||||
const method = methodValue;
|
||||
if (method === 'elicitation/create') {
|
||||
const wrappedHandler = async (request, extra) => {
|
||||
const validatedRequest = (0, zod_compat_js_1.safeParse)(types_js_1.ElicitRequestSchema, request);
|
||||
if (!validatedRequest.success) {
|
||||
// Type guard: if success is false, error is guaranteed to exist
|
||||
const errorMessage = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
|
||||
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid elicitation request: ${errorMessage}`);
|
||||
}
|
||||
const { params } = validatedRequest.data;
|
||||
params.mode = params.mode ?? 'form';
|
||||
const { supportsFormMode, supportsUrlMode } = getSupportedElicitationModes(this._capabilities.elicitation);
|
||||
if (params.mode === 'form' && !supportsFormMode) {
|
||||
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Client does not support form-mode elicitation requests');
|
||||
}
|
||||
if (params.mode === 'url' && !supportsUrlMode) {
|
||||
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Client does not support URL-mode elicitation requests');
|
||||
}
|
||||
const result = await Promise.resolve(handler(request, extra));
|
||||
// When task creation is requested, validate and return CreateTaskResult
|
||||
if (params.task) {
|
||||
const taskValidationResult = (0, zod_compat_js_1.safeParse)(types_js_1.CreateTaskResultSchema, result);
|
||||
if (!taskValidationResult.success) {
|
||||
const errorMessage = taskValidationResult.error instanceof Error
|
||||
? taskValidationResult.error.message
|
||||
: String(taskValidationResult.error);
|
||||
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
|
||||
}
|
||||
return taskValidationResult.data;
|
||||
}
|
||||
// For non-task requests, validate against ElicitResultSchema
|
||||
const validationResult = (0, zod_compat_js_1.safeParse)(types_js_1.ElicitResultSchema, result);
|
||||
if (!validationResult.success) {
|
||||
// Type guard: if success is false, error is guaranteed to exist
|
||||
const errorMessage = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
|
||||
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid elicitation result: ${errorMessage}`);
|
||||
}
|
||||
const validatedResult = validationResult.data;
|
||||
const requestedSchema = params.mode === 'form' ? params.requestedSchema : undefined;
|
||||
if (params.mode === 'form' && validatedResult.action === 'accept' && validatedResult.content && requestedSchema) {
|
||||
if (this._capabilities.elicitation?.form?.applyDefaults) {
|
||||
try {
|
||||
applyElicitationDefaults(requestedSchema, validatedResult.content);
|
||||
}
|
||||
catch {
|
||||
// gracefully ignore errors in default application
|
||||
}
|
||||
}
|
||||
}
|
||||
return validatedResult;
|
||||
};
|
||||
// Install the wrapped handler
|
||||
return super.setRequestHandler(requestSchema, wrappedHandler);
|
||||
}
|
||||
if (method === 'sampling/createMessage') {
|
||||
const wrappedHandler = async (request, extra) => {
|
||||
const validatedRequest = (0, zod_compat_js_1.safeParse)(types_js_1.CreateMessageRequestSchema, request);
|
||||
if (!validatedRequest.success) {
|
||||
const errorMessage = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
|
||||
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid sampling request: ${errorMessage}`);
|
||||
}
|
||||
const { params } = validatedRequest.data;
|
||||
const result = await Promise.resolve(handler(request, extra));
|
||||
// When task creation is requested, validate and return CreateTaskResult
|
||||
if (params.task) {
|
||||
const taskValidationResult = (0, zod_compat_js_1.safeParse)(types_js_1.CreateTaskResultSchema, result);
|
||||
if (!taskValidationResult.success) {
|
||||
const errorMessage = taskValidationResult.error instanceof Error
|
||||
? taskValidationResult.error.message
|
||||
: String(taskValidationResult.error);
|
||||
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
|
||||
}
|
||||
return taskValidationResult.data;
|
||||
}
|
||||
// For non-task requests, validate against appropriate schema based on tools presence
|
||||
const hasTools = params.tools || params.toolChoice;
|
||||
const resultSchema = hasTools ? types_js_1.CreateMessageResultWithToolsSchema : types_js_1.CreateMessageResultSchema;
|
||||
const validationResult = (0, zod_compat_js_1.safeParse)(resultSchema, result);
|
||||
if (!validationResult.success) {
|
||||
const errorMessage = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
|
||||
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Invalid sampling result: ${errorMessage}`);
|
||||
}
|
||||
return validationResult.data;
|
||||
};
|
||||
// Install the wrapped handler
|
||||
return super.setRequestHandler(requestSchema, wrappedHandler);
|
||||
}
|
||||
// Other handlers use default behavior
|
||||
return super.setRequestHandler(requestSchema, handler);
|
||||
}
|
||||
assertCapability(capability, method) {
|
||||
if (!this._serverCapabilities?.[capability]) {
|
||||
var _a;
|
||||
if (!((_a = this._serverCapabilities) === null || _a === void 0 ? void 0 : _a[capability])) {
|
||||
throw new Error(`Server does not support ${capability} (required for ${method})`);
|
||||
}
|
||||
}
|
||||
@@ -295,12 +70,12 @@ class Client extends protocol_js_1.Protocol {
|
||||
}
|
||||
try {
|
||||
const result = await this.request({
|
||||
method: 'initialize',
|
||||
method: "initialize",
|
||||
params: {
|
||||
protocolVersion: types_js_1.LATEST_PROTOCOL_VERSION,
|
||||
capabilities: this._capabilities,
|
||||
clientInfo: this._clientInfo
|
||||
}
|
||||
clientInfo: this._clientInfo,
|
||||
},
|
||||
}, types_js_1.InitializeResultSchema, options);
|
||||
if (result === undefined) {
|
||||
throw new Error(`Server sent invalid initialize result: ${result}`);
|
||||
@@ -316,13 +91,8 @@ class Client extends protocol_js_1.Protocol {
|
||||
}
|
||||
this._instructions = result.instructions;
|
||||
await this.notification({
|
||||
method: 'notifications/initialized'
|
||||
method: "notifications/initialized",
|
||||
});
|
||||
// Set up list changed handlers now that we know server capabilities
|
||||
if (this._pendingListChangedConfig) {
|
||||
this._setupListChangedHandlers(this._pendingListChangedConfig);
|
||||
this._pendingListChangedConfig = undefined;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// Disconnect if initialization fails.
|
||||
@@ -349,154 +119,124 @@ class Client extends protocol_js_1.Protocol {
|
||||
return this._instructions;
|
||||
}
|
||||
assertCapabilityForMethod(method) {
|
||||
var _a, _b, _c, _d, _e;
|
||||
switch (method) {
|
||||
case 'logging/setLevel':
|
||||
if (!this._serverCapabilities?.logging) {
|
||||
case "logging/setLevel":
|
||||
if (!((_a = this._serverCapabilities) === null || _a === void 0 ? void 0 : _a.logging)) {
|
||||
throw new Error(`Server does not support logging (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
case 'prompts/get':
|
||||
case 'prompts/list':
|
||||
if (!this._serverCapabilities?.prompts) {
|
||||
case "prompts/get":
|
||||
case "prompts/list":
|
||||
if (!((_b = this._serverCapabilities) === null || _b === void 0 ? void 0 : _b.prompts)) {
|
||||
throw new Error(`Server does not support prompts (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
case 'resources/list':
|
||||
case 'resources/templates/list':
|
||||
case 'resources/read':
|
||||
case 'resources/subscribe':
|
||||
case 'resources/unsubscribe':
|
||||
if (!this._serverCapabilities?.resources) {
|
||||
case "resources/list":
|
||||
case "resources/templates/list":
|
||||
case "resources/read":
|
||||
case "resources/subscribe":
|
||||
case "resources/unsubscribe":
|
||||
if (!((_c = this._serverCapabilities) === null || _c === void 0 ? void 0 : _c.resources)) {
|
||||
throw new Error(`Server does not support resources (required for ${method})`);
|
||||
}
|
||||
if (method === 'resources/subscribe' && !this._serverCapabilities.resources.subscribe) {
|
||||
if (method === "resources/subscribe" &&
|
||||
!this._serverCapabilities.resources.subscribe) {
|
||||
throw new Error(`Server does not support resource subscriptions (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
case 'tools/call':
|
||||
case 'tools/list':
|
||||
if (!this._serverCapabilities?.tools) {
|
||||
case "tools/call":
|
||||
case "tools/list":
|
||||
if (!((_d = this._serverCapabilities) === null || _d === void 0 ? void 0 : _d.tools)) {
|
||||
throw new Error(`Server does not support tools (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
case 'completion/complete':
|
||||
if (!this._serverCapabilities?.completions) {
|
||||
case "completion/complete":
|
||||
if (!((_e = this._serverCapabilities) === null || _e === void 0 ? void 0 : _e.completions)) {
|
||||
throw new Error(`Server does not support completions (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
case 'initialize':
|
||||
case "initialize":
|
||||
// No specific capability required for initialize
|
||||
break;
|
||||
case 'ping':
|
||||
case "ping":
|
||||
// No specific capability required for ping
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertNotificationCapability(method) {
|
||||
var _a;
|
||||
switch (method) {
|
||||
case 'notifications/roots/list_changed':
|
||||
if (!this._capabilities.roots?.listChanged) {
|
||||
case "notifications/roots/list_changed":
|
||||
if (!((_a = this._capabilities.roots) === null || _a === void 0 ? void 0 : _a.listChanged)) {
|
||||
throw new Error(`Client does not support roots list changed notifications (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
case 'notifications/initialized':
|
||||
case "notifications/initialized":
|
||||
// No specific capability required for initialized
|
||||
break;
|
||||
case 'notifications/cancelled':
|
||||
case "notifications/cancelled":
|
||||
// Cancellation notifications are always allowed
|
||||
break;
|
||||
case 'notifications/progress':
|
||||
case "notifications/progress":
|
||||
// Progress notifications are always allowed
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertRequestHandlerCapability(method) {
|
||||
// Task handlers are registered in Protocol constructor before _capabilities is initialized
|
||||
// Skip capability check for task methods during initialization
|
||||
if (!this._capabilities) {
|
||||
return;
|
||||
}
|
||||
switch (method) {
|
||||
case 'sampling/createMessage':
|
||||
case "sampling/createMessage":
|
||||
if (!this._capabilities.sampling) {
|
||||
throw new Error(`Client does not support sampling capability (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
case 'elicitation/create':
|
||||
case "elicitation/create":
|
||||
if (!this._capabilities.elicitation) {
|
||||
throw new Error(`Client does not support elicitation capability (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
case 'roots/list':
|
||||
case "roots/list":
|
||||
if (!this._capabilities.roots) {
|
||||
throw new Error(`Client does not support roots capability (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
case 'tasks/get':
|
||||
case 'tasks/list':
|
||||
case 'tasks/result':
|
||||
case 'tasks/cancel':
|
||||
if (!this._capabilities.tasks) {
|
||||
throw new Error(`Client does not support tasks capability (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
case 'ping':
|
||||
case "ping":
|
||||
// No specific capability required for ping
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTaskCapability(method) {
|
||||
(0, helpers_js_1.assertToolsCallTaskCapability)(this._serverCapabilities?.tasks?.requests, method, 'Server');
|
||||
}
|
||||
assertTaskHandlerCapability(method) {
|
||||
// Task handlers are registered in Protocol constructor before _capabilities is initialized
|
||||
// Skip capability check for task methods during initialization
|
||||
if (!this._capabilities) {
|
||||
return;
|
||||
}
|
||||
(0, helpers_js_1.assertClientRequestTaskCapability)(this._capabilities.tasks?.requests, method, 'Client');
|
||||
}
|
||||
async ping(options) {
|
||||
return this.request({ method: 'ping' }, types_js_1.EmptyResultSchema, options);
|
||||
return this.request({ method: "ping" }, types_js_1.EmptyResultSchema, options);
|
||||
}
|
||||
async complete(params, options) {
|
||||
return this.request({ method: 'completion/complete', params }, types_js_1.CompleteResultSchema, options);
|
||||
return this.request({ method: "completion/complete", params }, types_js_1.CompleteResultSchema, options);
|
||||
}
|
||||
async setLoggingLevel(level, options) {
|
||||
return this.request({ method: 'logging/setLevel', params: { level } }, types_js_1.EmptyResultSchema, options);
|
||||
return this.request({ method: "logging/setLevel", params: { level } }, types_js_1.EmptyResultSchema, options);
|
||||
}
|
||||
async getPrompt(params, options) {
|
||||
return this.request({ method: 'prompts/get', params }, types_js_1.GetPromptResultSchema, options);
|
||||
return this.request({ method: "prompts/get", params }, types_js_1.GetPromptResultSchema, options);
|
||||
}
|
||||
async listPrompts(params, options) {
|
||||
return this.request({ method: 'prompts/list', params }, types_js_1.ListPromptsResultSchema, options);
|
||||
return this.request({ method: "prompts/list", params }, types_js_1.ListPromptsResultSchema, options);
|
||||
}
|
||||
async listResources(params, options) {
|
||||
return this.request({ method: 'resources/list', params }, types_js_1.ListResourcesResultSchema, options);
|
||||
return this.request({ method: "resources/list", params }, types_js_1.ListResourcesResultSchema, options);
|
||||
}
|
||||
async listResourceTemplates(params, options) {
|
||||
return this.request({ method: 'resources/templates/list', params }, types_js_1.ListResourceTemplatesResultSchema, options);
|
||||
return this.request({ method: "resources/templates/list", params }, types_js_1.ListResourceTemplatesResultSchema, options);
|
||||
}
|
||||
async readResource(params, options) {
|
||||
return this.request({ method: 'resources/read', params }, types_js_1.ReadResourceResultSchema, options);
|
||||
return this.request({ method: "resources/read", params }, types_js_1.ReadResourceResultSchema, options);
|
||||
}
|
||||
async subscribeResource(params, options) {
|
||||
return this.request({ method: 'resources/subscribe', params }, types_js_1.EmptyResultSchema, options);
|
||||
return this.request({ method: "resources/subscribe", params }, types_js_1.EmptyResultSchema, options);
|
||||
}
|
||||
async unsubscribeResource(params, options) {
|
||||
return this.request({ method: 'resources/unsubscribe', params }, types_js_1.EmptyResultSchema, options);
|
||||
return this.request({ method: "resources/unsubscribe", params }, types_js_1.EmptyResultSchema, options);
|
||||
}
|
||||
/**
|
||||
* Calls a tool and waits for the result. Automatically validates structured output if the tool has an outputSchema.
|
||||
*
|
||||
* For task-based execution with streaming behavior, use client.experimental.tasks.callToolStream() instead.
|
||||
*/
|
||||
async callTool(params, resultSchema = types_js_1.CallToolResultSchema, options) {
|
||||
// Guard: required-task tools need experimental API
|
||||
if (this.isToolTaskRequired(params.name)) {
|
||||
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, `Tool "${params.name}" requires task-based execution. Use client.experimental.tasks.callToolStream() instead.`);
|
||||
}
|
||||
const result = await this.request({ method: 'tools/call', params }, resultSchema, options);
|
||||
const result = await this.request({ method: "tools/call", params }, resultSchema, options);
|
||||
// Check if the tool has an outputSchema
|
||||
const validator = this.getToolOutputValidator(params.name);
|
||||
if (validator) {
|
||||
@@ -507,10 +247,10 @@ class Client extends protocol_js_1.Protocol {
|
||||
// Only validate structured content if present (not when there's an error)
|
||||
if (result.structuredContent) {
|
||||
try {
|
||||
// Validate the structured content against the schema
|
||||
const validationResult = validator(result.structuredContent);
|
||||
if (!validationResult.valid) {
|
||||
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Structured content does not match the tool's output schema: ${validationResult.errorMessage}`);
|
||||
// Validate the structured content (which is already an object) against the schema
|
||||
const isValid = validator(result.structuredContent);
|
||||
if (!isValid) {
|
||||
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Structured content does not match the tool's output schema: ${this._ajv.errorsText(validator.errors)}`);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
@@ -523,106 +263,32 @@ class Client extends protocol_js_1.Protocol {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
isToolTask(toolName) {
|
||||
if (!this._serverCapabilities?.tasks?.requests?.tools?.call) {
|
||||
return false;
|
||||
}
|
||||
return this._cachedKnownTaskTools.has(toolName);
|
||||
}
|
||||
/**
|
||||
* Check if a tool requires task-based execution.
|
||||
* Unlike isToolTask which includes 'optional' tools, this only checks for 'required'.
|
||||
*/
|
||||
isToolTaskRequired(toolName) {
|
||||
return this._cachedRequiredTaskTools.has(toolName);
|
||||
}
|
||||
/**
|
||||
* Cache validators for tool output schemas.
|
||||
* Called after listTools() to pre-compile validators for better performance.
|
||||
*/
|
||||
cacheToolMetadata(tools) {
|
||||
cacheToolOutputSchemas(tools) {
|
||||
this._cachedToolOutputValidators.clear();
|
||||
this._cachedKnownTaskTools.clear();
|
||||
this._cachedRequiredTaskTools.clear();
|
||||
for (const tool of tools) {
|
||||
// If the tool has an outputSchema, create and cache the validator
|
||||
// If the tool has an outputSchema, create and cache the Ajv validator
|
||||
if (tool.outputSchema) {
|
||||
const toolValidator = this._jsonSchemaValidator.getValidator(tool.outputSchema);
|
||||
this._cachedToolOutputValidators.set(tool.name, toolValidator);
|
||||
}
|
||||
// If the tool supports task-based execution, cache that information
|
||||
const taskSupport = tool.execution?.taskSupport;
|
||||
if (taskSupport === 'required' || taskSupport === 'optional') {
|
||||
this._cachedKnownTaskTools.add(tool.name);
|
||||
}
|
||||
if (taskSupport === 'required') {
|
||||
this._cachedRequiredTaskTools.add(tool.name);
|
||||
try {
|
||||
const validator = this._ajv.compile(tool.outputSchema);
|
||||
this._cachedToolOutputValidators.set(tool.name, validator);
|
||||
}
|
||||
catch (_a) {
|
||||
// Ignore schema compilation errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get cached validator for a tool
|
||||
*/
|
||||
getToolOutputValidator(toolName) {
|
||||
return this._cachedToolOutputValidators.get(toolName);
|
||||
}
|
||||
async listTools(params, options) {
|
||||
const result = await this.request({ method: 'tools/list', params }, types_js_1.ListToolsResultSchema, options);
|
||||
const result = await this.request({ method: "tools/list", params }, types_js_1.ListToolsResultSchema, options);
|
||||
// Cache the tools and their output schemas for future validation
|
||||
this.cacheToolMetadata(result.tools);
|
||||
this.cacheToolOutputSchemas(result.tools);
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Set up a single list changed handler.
|
||||
* @internal
|
||||
*/
|
||||
_setupListChangedHandler(listType, notificationSchema, options, fetcher) {
|
||||
// Validate options using Zod schema (validates autoRefresh and debounceMs)
|
||||
const parseResult = types_js_1.ListChangedOptionsBaseSchema.safeParse(options);
|
||||
if (!parseResult.success) {
|
||||
throw new Error(`Invalid ${listType} listChanged options: ${parseResult.error.message}`);
|
||||
}
|
||||
// Validate callback
|
||||
if (typeof options.onChanged !== 'function') {
|
||||
throw new Error(`Invalid ${listType} listChanged options: onChanged must be a function`);
|
||||
}
|
||||
const { autoRefresh, debounceMs } = parseResult.data;
|
||||
const { onChanged } = options;
|
||||
const refresh = async () => {
|
||||
if (!autoRefresh) {
|
||||
onChanged(null, null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const items = await fetcher();
|
||||
onChanged(null, items);
|
||||
}
|
||||
catch (e) {
|
||||
const error = e instanceof Error ? e : new Error(String(e));
|
||||
onChanged(error, null);
|
||||
}
|
||||
};
|
||||
const handler = () => {
|
||||
if (debounceMs) {
|
||||
// Clear any pending debounce timer for this list type
|
||||
const existingTimer = this._listChangedDebounceTimers.get(listType);
|
||||
if (existingTimer) {
|
||||
clearTimeout(existingTimer);
|
||||
}
|
||||
// Set up debounced refresh
|
||||
const timer = setTimeout(refresh, debounceMs);
|
||||
this._listChangedDebounceTimers.set(listType, timer);
|
||||
}
|
||||
else {
|
||||
// No debounce, refresh immediately
|
||||
refresh();
|
||||
}
|
||||
};
|
||||
// Register notification handler
|
||||
this.setNotificationHandler(notificationSchema, handler);
|
||||
}
|
||||
async sendRootsListChanged() {
|
||||
return this.notification({ method: 'notifications/roots/list_changed' });
|
||||
return this.notification({ method: "notifications/roots/list_changed" });
|
||||
}
|
||||
}
|
||||
exports.Client = Client;
|
||||
|
||||
Reference in New Issue
Block a user