Brunobkr/llama.cpp_AlgMor24_github
ΩFFFΣLLIa • llama.cpp • AlgMor24 ██████╗ ███████╗███████╗███████╗██╗ ██╗ ██╗ █████╗ ██╔═══██╗██╔════╝██╔════╝██╔════╝██║ ██║ ██║██╔══██╗ ██║ ██║█████╗ █████╗ █████╗ ██║ ██║ ██║███████║ ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██║ ██║ ██║██╔══██║ ╚██████╔╝██║ ██║ ███████╗███████╗███████╗██║██║ ██║ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝╚══════╝╚═╝╚═╝ ╚═╝ High-Performance LLM / VLM Inference & Autonomous Agentic Ecosystem… See the full description on the dataset page: https://huggingface.co/datasets/Brunobkr/llama.cpp_AlgMor24_github.
03k
1/* eslint-disable jsdoc/no-undefined-types */2/**3 * @import { StandardSchemaV1 } from "@standard-schema/spec";4 * @import SqidsType from "sqids";5 * @import { JSONRPCRequest, JSONRPCParams } from "json-rpc-2.0";6 * @import { ExtractURITemplateVariables } from "./internal/uri-template.js";7 * @import { CallToolResult as CallToolResultType, ReadResourceResult as ReadResourceResultType, GetPromptResult as GetPromptResultType, ServerInfo as ServerInfoType, ClientCapabilities as ClientCapabilitiesType, JSONRPCRequest as JSONRPCRequestType, JSONRPCResponse, CreateMessageRequestParams as CreateMessageRequestParamsType, CreateMessageResult as CreateMessageResultType, Resource as ResourceType, LoggingLevel as LoggingLevelType, ToolAnnotations, ClientInfo as ClientInfoType, ElicitResult as ElicitResultType, Icons as IconsType, JSONRPCMessage, InitializeResult as InitializeResultType, ListToolsResult as ListToolsResultType, ListPromptsResult as ListPromptsResultType, ListResourceTemplatesResult as ListResourceTemplatesResultType, ListResourcesResult as ListResourcesResultType, CompleteResult as CompleteResultType } from "./validation/index.js";8 * @import { Tool, Completion, Prompt, StoredResource, ServerOptions, SubscriptionsKeys, ChangedArgs, McpEvents, AllSame, TemplateOptions } from "./internal/internal.js";9 * @import { CreatedTool, ToolOptions, CreatedPrompt, PromptOptions, CreatedResource, CreatedTemplate, ResourceOptions } from "./internal/internal.js";10 */11import { JSONRPCClient, JSONRPCServer } from 'json-rpc-2.0';12import { AsyncLocalStorage } from 'node:async_hooks';13import { UriTemplateMatcher } from 'uri-template-matcher';14import * as v from 'valibot';15import {16 CallToolResultSchema,17 CompleteResultSchema,18 CreateMessageRequestParamsSchema,19 CreateMessageResultSchema,20 GetPromptResultSchema,21 InitializeRequestParamsSchema,22 JSONRPCNotificationSchema,23 JSONRPCRequestSchema,24 JSONRPCResponseSchema,25 McpError,26 ReadResourceResultSchema,27 ElicitResultSchema,28 JSONRPCErrorSchema,29} from './validation/index.js';30import {31 get_supported_versions,32 negotiate_protocol_version,33} from './validation/version.js';34import { should_version_negotiation_fail } from './validation/version.js';35import { event } from './internal/utils.js';36 37/**38 * Information about a validated access token, provided to request handlers.39 * @typedef {Object} AuthInfo40 * @property {string} token - The access token.41 * @property {string} clientId - The client ID associated with this token.42 * @property {string[]} scopes - Scopes associated with this token.43 * @property {number} [expiresAt] - When the token expires (in seconds since epoch).44 * @property {URL} [resource] - The RFC 8707 resource server identifier for which this token is valid.45 * If set, this MUST match the MCP server's resource identifier (minus hash fragment).46 * @property {Record<string, unknown>} [extra] - Additional data associated with the token.47 * This field should be used for any additional data that needs to be attached to the auth info.48 */49 50/**51 * @template {Record<string, unknown> | undefined} [TCustom=undefined]52 * @typedef {Object} Context53 * @property {string} [sessionId]54 * @property {{ clientCapabilities?: ClientCapabilitiesType, clientInfo?: ClientInfoType, logLevel?: LoggingLevel }} [sessionInfo]55 * @property {AuthInfo} [auth]56 * @property {TCustom} [custom]57 */58 59/**60 * @typedef {IconsType} Icons61 */62 63/**64 * @typedef {Record<SubscriptionsKeys, string[]>} Subscriptions65 */66 67/**68 * @template {Record<string, unknown> | undefined} TStructuredContent69 * @typedef {CallToolResultType<TStructuredContent>} CallToolResult70 */71 72/**73 * @typedef {ReadResourceResultType} ReadResourceResult74 */75 76/**77 * @typedef {GetPromptResultType} GetPromptResult78 */79 80/**81 * @typedef {ClientCapabilitiesType} ClientCapabilities82 */83 84/**85 * @typedef {ServerInfoType} ServerInfo86 */87 88/**89 * @typedef {CreateMessageRequestParamsType} CreateMessageRequestParams90 */91 92/**93 * @typedef {CreateMessageResultType} CreateMessageResult94 */95 96/**97 * @typedef {ResourceType} Resource98 */99 100/**101 * @typedef {LoggingLevelType} LoggingLevel102 */103 104/**105 * @typedef {ClientInfoType} ClientInfo106 */107 108/**109 * @typedef {ElicitResultType} ElicitResult110 */111 112/**113 * @typedef {InitializeResultType} InitializeResult114 */115 116/**117 * @typedef {ListToolsResultType} ListToolsResult118 */119 120/**121 * @typedef {ListPromptsResultType} ListPromptsResult122 */123 124/**125 * @typedef {ListResourceTemplatesResultType} ListResourceTemplatesResult126 */127 128/**129 * @typedef {ListResourcesResultType} ListResourcesResult130 */131/**132 * @typedef {CompleteResultType} CompleteResult133 */134 135/**136 * @type {SqidsType | undefined}137 */138let Sqids;139 140async function get_sqids() {141 if (!Sqids) {142 Sqids = new (await import('sqids')).default();143 }144 return Sqids;145}146 147/**148 * Encode a cursor for pagination149 * @param {number} offset150 */151async function encode_cursor(offset) {152 return (await get_sqids()).encode([offset]);153}154 155/**156 * Decode a cursor from pagination157 * @param {string} cursor158 */159async function decode_cursor(cursor) {160 const [decoded] = (await get_sqids()).decode(cursor);161 return decoded;162}163 164/**165 * @param {()=>boolean | Promise<boolean>} enabled166 */167async function safe_enabled(enabled) {168 try {169 return await enabled();170 } catch {171 return false;172 }173}174 175/**176 * @template {StandardSchemaV1 | undefined} [StandardSchema=undefined]177 * @template {Record<string, unknown> | undefined} [CustomContext=undefined]178 */179export class McpServer {180 #server = new JSONRPCServer();181 /**182 * @type {JSONRPCClient<"broadcast" | "standalone"> | undefined}183 */184 #client;185 #options;186 /**187 * @type {Map<string, Tool<any, any>>}188 */189 #tools = new Map();190 /**191 * @type {Map<string, Prompt<any>>}192 */193 #prompts = new Map();194 /**195 * @type {Map<string, StoredResource>}196 */197 #resources = new Map();198 #templates = new UriTemplateMatcher();199 /**200 * @type {Array<{uri: string, name?: string}>}201 */202 roots = [];203 /**204 * @type {{ [ref: string]: Map<string, Partial<Record<string, Completion>>> }}205 */206 #completions = {207 'ref/prompt': new Map(),208 'ref/resource': new Map(),209 };210 211 #event_target = new EventTarget();212 213 /**214 * @type {AsyncLocalStorage<Context<CustomContext> & { progress_token?: string }>}215 */216 #ctx_storage = new AsyncLocalStorage();217 218 /**219 * @param {ServerInfo} server_info220 * @param {ServerOptions<StandardSchema>} options221 */222 constructor(server_info, options) {223 this.#options = options;224 this.#server.addMethod('initialize', (initialize_request) => {225 try {226 // Validate basic request format227 const validated_initialize = v.parse(228 InitializeRequestParamsSchema,229 initialize_request,230 );231 232 // Validate protocol version format233 if (234 should_version_negotiation_fail(235 validated_initialize.protocolVersion,236 )237 ) {238 // Return JSON-RPC error for invalid protocol version format239 const error = new McpError(240 -32602,241 'Invalid protocol version format',242 );243 throw error;244 }245 246 // Negotiate protocol version247 const negotiated_version = negotiate_protocol_version(248 validated_initialize.protocolVersion,249 );250 251 // Dispatch initialization event252 this.#event_target.dispatchEvent(253 event('initialize', validated_initialize),254 );255 256 // Return server response with negotiated version and capabilities257 return {258 protocolVersion: negotiated_version,259 ...options,260 serverInfo: server_info,261 };262 } catch (error) {263 // Enhanced error handling for initialization failures264 if (error instanceof McpError) {265 // Already has JSON-RPC error code, re-throw266 throw error;267 }268 269 if (270 /** @type {Error} */ (error).message?.includes(271 'Protocol version',272 )273 ) {274 const rpc_error = new McpError(275 -32602,276 `Protocol version validation failed: ${/** @type {Error} */ (error).message}. Server supports: ${get_supported_versions().join(', ')}`,277 );278 throw rpc_error;279 }280 281 // General initialization error282 const rpc_error = new McpError(283 -32603,284 `Initialization failed: ${/** @type {Error} */ (error).message}`,285 );286 throw rpc_error;287 }288 });289 this.#server.addMethod('ping', () => {290 return {};291 });292 this.#server.addMethod('notifications/initialized', () => {293 return null;294 });295 this.#init_tools();296 this.#init_prompts();297 this.#init_resources();298 this.#init_roots();299 this.#init_completion();300 this.#init_logging();301 }302 303 /**304 * Utility method to specify the type of the custom context for this server instance without the need to specify the standard schema type.305 * @example306 * const server = new McpServer({ ... }, { ... }).withContext<{ name: string }>();307 * @template {Record<string, unknown>} TCustom308 * @returns {McpServer<StandardSchema, TCustom>}309 */310 withContext() {311 return /** @type {McpServer<StandardSchema, TCustom>} */ (312 /** @type {unknown} */ (this)313 );314 }315 316 get #progress_token() {317 return this.#ctx_storage.getStore()?.progress_token;318 }319 320 /**321 * The context of the current request, include the session ID, any auth information, and custom data.322 * @type {Context<CustomContext>}323 */324 get ctx() {325 // eslint-disable-next-line no-unused-vars326 const { progress_token, ...rest } = this.#ctx_storage.getStore() ?? {};327 return rest;328 }329 330 get #client_capabilities() {331 return this.#ctx_storage.getStore()?.sessionInfo?.clientCapabilities;332 }333 334 /**335 * Get the client information (name, version, etc.) of the client that initiated the current request...useful if you want to do something different based on the client.336 * @deprecated Use `server.ctx.sessionInfo.clientInfo` instead.337 */338 currentClientInfo() {339 return this.#ctx_storage.getStore()?.sessionInfo?.clientInfo;340 }341 342 /**343 * Get the client capabilities of the client that initiated the current request, you can use this to verify the client support something before invoking the respective method.344 * @deprecated Use `server.ctx.sessionInfo.clientCapabilities` instead.345 */346 currentClientCapabilities() {347 return this.#client_capabilities;348 }349 350 #lazyily_create_client() {351 if (!this.#client) {352 this.#client = new JSONRPCClient((payload, kind) => {353 if (kind === 'broadcast') {354 this.#event_target.dispatchEvent(355 event('broadcast', { request: payload }),356 );357 return;358 }359 this.#event_target.dispatchEvent(360 event('send', { request: payload }),361 );362 });363 }364 }365 366 /**367 * @template {keyof McpEvents} TEvent368 * @param {TEvent} event369 * @param {McpEvents[TEvent]} callback370 * @param {AddEventListenerOptions} [options]371 */372 on(event, callback, options) {373 if (event === 'send' || event === 'broadcast') {374 this.#lazyily_create_client();375 }376 377 /**378 * @param {Event} e379 */380 const listener = (e) => {381 callback(/** @type {CustomEvent} */ (e).detail);382 };383 384 this.#event_target.addEventListener(event, listener, options);385 386 return () => {387 this.#event_target.removeEventListener(event, listener, options);388 };389 }390 391 /**392 * @param {string} method393 * @param {JSONRPCParams} [params]394 * @param {"broadcast" | "standalone"} [kind]395 */396 #notify(method, params, kind = 'standalone') {397 this.#client?.notify(method, params, kind);398 }399 400 /**401 *402 */403 #init_tools() {404 if (!this.#options.capabilities?.tools) return;405 this.#server.addMethod('tools/list', async ({ cursor } = {}) => {406 const all_tools = (407 await Promise.all(408 [...this.#tools].map(async ([name, tool]) => {409 if (410 tool.enabled != null &&411 (await safe_enabled(tool.enabled)) === false412 )413 return null;414 return {415 name,416 title: tool.title || tool.description,417 description: tool.description,418 icons: tool.icons,419 _meta: tool._meta,420 inputSchema:421 tool.schema && this.#options.adapter422 ? await this.#options.adapter.toJsonSchema(423 tool.schema,424 )425 : { type: 'object', properties: {} },426 ...(tool.outputSchema && this.#options.adapter427 ? {428 outputSchema:429 await this.#options.adapter.toJsonSchema(430 tool.outputSchema,431 ),432 }433 : {}),434 ...(tool.annotations435 ? {436 annotations: tool.annotations,437 }438 : {}),439 };440 }),441 )442 ).filter((tool) => tool !== null);443 444 const pagination_options = this.#options.pagination?.tools;445 if (!pagination_options || pagination_options.size == null) {446 return { tools: all_tools };447 }448 449 const page_length = pagination_options.size;450 const offset = cursor ? await decode_cursor(cursor) : 0;451 const start_index = offset;452 const end_index = start_index + page_length;453 454 const tools = all_tools.slice(start_index, end_index);455 const has_next = end_index < all_tools.length;456 const next_cursor = has_next457 ? await encode_cursor(end_index)458 : null;459 460 return {461 tools,462 ...(next_cursor && { nextCursor: next_cursor }),463 };464 });465 this.#server.addMethod(466 'tools/call',467 async ({ name, arguments: args }) => {468 const tool = this.#tools.get(name);469 if (!tool) {470 return /** @type {CallToolResult<any>} */ ({471 isError: true,472 content: [473 {474 type: 'text',475 text: `Tool ${name} not found`,476 },477 ],478 });479 }480 481 // Validate input arguments if schema is provided482 let validated_args = args;483 if (tool.schema) {484 let validation_result =485 tool.schema['~standard'].validate(args);486 if (validation_result instanceof Promise)487 validation_result = await validation_result;488 if (validation_result.issues) {489 return /** @type {CallToolResult<any>} */ ({490 isError: true,491 content: [492 {493 type: 'text',494 text: `Invalid arguments for tool ${name}: ${JSON.stringify(validation_result.issues)}`,495 },496 ],497 });498 }499 validated_args = validation_result.value;500 }501 502 // Execute the tool503 const tool_result = tool.schema504 ? await tool.execute(validated_args)505 : await tool.execute();506 507 // Parse the basic result structure508 const parsed_result = v.parse(509 CallToolResultSchema,510 tool_result,511 );512 513 // If tool has outputSchema, validate and populate structuredContent514 if (515 tool.outputSchema &&516 parsed_result.structuredContent !== undefined517 ) {518 let output_validation = tool.outputSchema[519 '~standard'520 ].validate(parsed_result.structuredContent);521 if (output_validation instanceof Promise)522 output_validation = await output_validation;523 if (output_validation.issues) {524 return /** @type {CallToolResult<any>} */ ({525 isError: true,526 content: [527 {528 type: 'text',529 text: `Tool ${name} returned invalid structured content: ${JSON.stringify(output_validation.issues)}`,530 },531 ],532 });533 }534 // Update with validated structured content535 parsed_result.structuredContent = output_validation.value;536 }537 538 return parsed_result;539 },540 );541 }542 /**543 *544 */545 #init_prompts() {546 if (!this.#options.capabilities?.prompts) return;547 this.#server.addMethod('prompts/list', async ({ cursor } = {}) => {548 const all_prompts = (549 await Promise.all(550 [...this.#prompts].map(async ([name, prompt]) => {551 if (552 prompt.enabled != null &&553 (await safe_enabled(prompt.enabled)) === false554 )555 return null;556 const arguments_schema =557 prompt.schema && this.#options.adapter558 ? await this.#options.adapter.toJsonSchema(559 prompt.schema,560 )561 : {562 type: 'object',563 properties:564 /** @type {Record<string, {description: string}>} */ ({}),565 required: [],566 };567 const keys = Object.keys(568 arguments_schema.properties ?? {},569 );570 const required = arguments_schema.required ?? [];571 return {572 name,573 title: prompt.title || prompt.description,574 icons: prompt.icons,575 description: prompt.description,576 arguments: keys.map((key) => {577 const property =578 arguments_schema.properties?.[key];579 const description =580 property && property !== true581 ? property.description582 : key;583 return {584 name: key,585 required: required.includes(key),586 description,587 };588 }),589 };590 }),591 )592 ).filter((prompt) => prompt !== null);593 594 const pagination_options = this.#options.pagination?.prompts;595 if (!pagination_options || pagination_options.size == null) {596 return { prompts: all_prompts };597 }598 599 const page_length = pagination_options.size;600 const offset = cursor ? await decode_cursor(cursor) : 0;601 const start_index = offset;602 const end_index = start_index + page_length;603 604 const prompts = all_prompts.slice(start_index, end_index);605 const has_next = end_index < all_prompts.length;606 const next_cursor = has_next607 ? await encode_cursor(end_index)608 : null;609 610 return {611 prompts,612 ...(next_cursor && { nextCursor: next_cursor }),613 };614 });615 this.#server.addMethod(616 'prompts/get',617 async ({ name, arguments: args }) => {618 const prompt = this.#prompts.get(name);619 if (!prompt) {620 throw new McpError(-32601, `Prompt ${name} not found`);621 }622 if (!prompt.schema) {623 return v.parse(624 GetPromptResultSchema,625 await prompt.execute(),626 );627 }628 let validated_args = prompt.schema['~standard'].validate(args);629 if (validated_args instanceof Promise)630 validated_args = await validated_args;631 if (validated_args.issues) {632 throw new McpError(633 -32602,634 `Invalid arguments for prompt ${name}: ${JSON.stringify(validated_args.issues)}`,635 );636 }637 return v.parse(638 GetPromptResultSchema,639 await prompt.execute(validated_args.value),640 );641 },642 );643 }644 /**645 *646 */647 #init_resources() {648 if (!this.#options.capabilities?.resources) return;649 650 if (this.#options.capabilities?.resources?.subscribe) {651 this.#server.addMethod('resources/subscribe', async ({ uri }) => {652 this.#event_target.dispatchEvent(653 event('subscription', { uri, action: 'add' }),654 );655 return {};656 });657 this.#server.addMethod('resources/unsubscribe', async ({ uri }) => {658 this.#event_target.dispatchEvent(659 event('subscription', { uri, action: 'remove' }),660 );661 return {};662 });663 }664 665 this.#server.addMethod('resources/list', async ({ cursor } = {}) => {666 const all_resources = [];667 668 // Add static resources669 for (const [uri, resource] of this.#resources) {670 if (!resource.template) {671 if (672 resource.enabled != null &&673 (await safe_enabled(resource.enabled)) === false674 )675 continue;676 all_resources.push({677 name: resource.name,678 title: resource.title || resource.description,679 description: resource.description,680 uri,681 mimeType: resource.mimeType,682 icons: resource.icons,683 });684 } else if (resource.list_resources) {685 if (686 resource.enabled != null &&687 (await safe_enabled(resource.enabled)) === false688 )689 continue;690 const template_resources = await resource.list_resources();691 all_resources.push(...template_resources);692 }693 }694 695 const pagination_options = this.#options.pagination?.resources;696 if (!pagination_options || pagination_options.size == null) {697 return { resources: all_resources };698 }699 700 const page_length = pagination_options.size;701 const offset = cursor ? await decode_cursor(cursor) : 0;702 const start_index = offset;703 const end_index = start_index + page_length;704 705 const resources = all_resources.slice(start_index, end_index);706 const has_next = end_index < all_resources.length;707 const next_cursor = has_next708 ? await encode_cursor(end_index)709 : null;710 711 return {712 resources,713 ...(next_cursor && { nextCursor: next_cursor }),714 };715 });716 this.#server.addMethod('resources/templates/list', async () => {717 return {718 resourceTemplates: (719 await Promise.all(720 [...this.#resources].map(async ([uri, resource]) => {721 if (!resource.template) return null;722 if (723 resource.enabled != null &&724 (await safe_enabled(resource.enabled)) === false725 )726 return null;727 return {728 name: resource.name,729 icons: resource.icons,730 title: resource.title || resource.description,731 description: resource.description,732 mimeType: resource.mimeType,733 uriTemplate: uri,734 };735 }),736 )737 ).filter((resource) => resource != null),738 };739 });740 this.#server.addMethod('resources/read', async ({ uri }) => {741 let resource = this.#resources.get(uri);742 let params;743 if (!resource) {744 const match = this.#templates.match(uri);745 if (match) {746 resource = this.#resources.get(match.template);747 params = match.params;748 }749 if (!resource) {750 throw new McpError(-32601, `Resource ${uri} not found`);751 }752 }753 if (resource.template) {754 if (!params)755 throw new McpError(756 -32602,757 'Missing parameters for template resource',758 );759 return v.parse(760 ReadResourceResultSchema,761 await resource.execute(uri, params),762 );763 }764 return v.parse(765 ReadResourceResultSchema,766 await resource.execute(uri),767 );768 });769 }770 /**771 *772 */773 #init_roots() {774 this.#server.addMethod('notifications/roots/list_changed', () => {775 this.#refresh_roots();776 return null;777 });778 }779 780 /**781 * Request roots list from client782 */783 async #refresh_roots() {784 if (!this.#client_capabilities?.roots) return;785 786 this.#lazyily_create_client();787 try {788 const response = await this.#client?.request(789 'roots/list',790 undefined,791 'standalone',792 );793 this.roots = response?.roots || [];794 } catch {795 // Client doesn't support roots or request failed796 this.roots = [];797 }798 }799 800 #init_completion() {801 this.#server.addMethod(802 'completion/complete',803 async ({ argument, ref, context }) => {804 const completions = this.#completions[ref.type];805 if (!completions) return null;806 const complete = completions.get(ref.uri ?? ref.name);807 if (!complete) return null;808 const actual_complete = complete[argument.name];809 if (!actual_complete) return null;810 return v.parse(811 CompleteResultSchema,812 await actual_complete(argument.value, context),813 );814 },815 );816 }817 818 #init_logging() {819 if (!this.#options.capabilities?.logging) return;820 821 this.#server.addMethod('logging/setLevel', ({ level }) => {822 this.#event_target.dispatchEvent(823 event('loglevelchange', { level }),824 );825 return {};826 });827 }828 829 #notify_tools_list_changed() {830 if (this.#options.capabilities?.tools?.listChanged) {831 this.#notify('notifications/tools/list_changed', {}, 'broadcast');832 }833 }834 835 #notify_prompts_list_changed() {836 if (this.#options.capabilities?.prompts?.listChanged) {837 this.#notify('notifications/prompts/list_changed', {}, 'broadcast');838 }839 }840 841 #notify_resources_list_changed() {842 if (this.#options.capabilities?.resources?.listChanged) {843 this.#notify(844 'notifications/resources/list_changed',845 {},846 'broadcast',847 );848 }849 }850 851 /**852 * Use the `defineTool` utility to create a reusable tool and pass it to this method to add it to the server.853 * @template {Array<CreatedTool<any, any>>} T854 * @template {T extends Array<CreatedTool<infer TSchema, infer TOutputSchema>> ? AllSame<TSchema, StandardSchema | undefined> extends true ? AllSame<TOutputSchema, StandardSchema | undefined> extends true ? T : never : never : never} U855 * @param {T & NoInfer<U>} tools856 */857 tools(tools) {858 for (const tool of tools) {859 this.tool(tool);860 }861 }862 863 /**864 * Use the `definePrompt` utility to create a reusable tool and pass it to this method to add it to the server.865 * @template {Array<CreatedPrompt<any>>} T866 * @template {T extends Array<CreatedPrompt<infer TSchema>> ? AllSame<TSchema, StandardSchema | undefined> extends true ? T : never : never} U867 * @param {T & NoInfer<U>} prompts868 */869 prompts(prompts) {870 for (const prompt of prompts) {871 this.prompt(prompt);872 }873 }874 875 /**876 * Use the `defineResource` utility to create a reusable resource and pass it to this method to add it to the server.877 *878 * @param {CreatedResource[]} resources879 */880 resources(resources) {881 for (const resource of resources) {882 this.resource(resource);883 }884 }885 886 /**887 * Use the `defineTemplate` utility to create a reusable template and pass it to this method to add it to the server.888 *889 * @param {CreatedTemplate<any>[]} templates890 */891 templates(templates) {892 for (const template of templates) {893 this.template(template);894 }895 }896 897 /**898 * Add a tool to the server. If you want to receive any input you need to provide a schema. The schema needs to be a valid Standard Schema V1 schema and needs to be an Object with the properties you need,899 * Use the description and title to help the LLM to understand what the tool does and when to use it. If you provide an outputSchema, you need to return a structuredContent that matches the schema.900 *901 * Tools will be invoked by the LLM when it thinks it needs to use them, you can use the annotations to provide additional information about the tool, like what it does, how to use it, etc.902 * @template {StandardSchema | undefined} [TSchema=undefined]903 * @template {StandardSchema | undefined} [TOutputSchema=undefined]904 * @overload905 * @param {CreatedTool<TSchema, TOutputSchema>} tool_or_options906 * @returns {void}907 */908 /**909 * Add a tool to the server. If you want to receive any input you need to provide a schema. The schema needs to be a valid Standard Schema V1 schema and needs to be an Object with the properties you need,910 * Use the description and title to help the LLM to understand what the tool does and when to use it. If you provide an outputSchema, you need to return a structuredContent that matches the schema.911 *912 * Tools will be invoked by the LLM when it thinks it needs to use them, you can use the annotations to provide additional information about the tool, like what it does, how to use it, etc.913 * @template {StandardSchema | undefined} [TSchema=undefined]914 * @template {StandardSchema | undefined} [TOutputSchema=undefined]915 * @overload916 * @param {ToolOptions<TSchema, TOutputSchema>} tool_or_options917 * @param {TSchema extends undefined ? (()=>Promise<CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>> | CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>) : ((input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>> | CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>)} execute918 * @returns {void}919 * */920 /**921 * Add a tool to the server. If you want to receive any input you need to provide a schema. The schema needs to be a valid Standard Schema V1 schema and needs to be an Object with the properties you need,922 * Use the description and title to help the LLM to understand what the tool does and when to use it. If you provide an outputSchema, you need to return a structuredContent that matches the schema.923 *924 * Tools will be invoked by the LLM when it thinks it needs to use them, you can use the annotations to provide additional information about the tool, like what it does, how to use it, etc.925 * @template {StandardSchema | undefined} [TSchema=undefined]926 * @template {StandardSchema | undefined} [TOutputSchema=undefined]927 * @param {CreatedTool<TSchema, TOutputSchema> | ToolOptions<TSchema, TOutputSchema>} tool_or_options928 * @param {undefined | TSchema extends undefined ? (()=>Promise<CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>> | CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>) : ((input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>> | CallToolResult<TOutputSchema extends undefined ? undefined : StandardSchemaV1.InferInput<TOutputSchema extends undefined ? never : TOutputSchema>>)} [execute]929 */930 tool(tool_or_options, execute) {931 if ('execute' in tool_or_options) {932 // @ts-expect-error typescript doesn't know about execute because of an egregious hack to prevent it933 // from showing in intellisense when declaring a tool inline934 execute = tool_or_options.execute;935 }936 this.#notify_tools_list_changed();937 const stored_tool = /** @type {Tool<any, any>} */ (tool_or_options);938 stored_tool.execute = /** @type {NonNullable<typeof execute>} */ (939 execute940 );941 this.#tools.set(tool_or_options.name, stored_tool);942 }943 944 /**945 * Add a prompt to the server. Prompts are used to provide the user with pre-defined messages that adds context to the LLM.946 * Use the description and title to help the user to understand what the prompt does and when to use it.947 *948 * A prompt can also have a schema that defines the input it expects, the user will be prompted to enter the inputs you request. It can also have a complete function949 * for each input that will be used to provide completions for the user.950 * @template {StandardSchema | undefined} [TSchema=undefined]951 * @overload952 * @param {CreatedPrompt<TSchema>} prompt_or_options953 * @returns {void}954 */955 /**956 * Add a prompt to the server. Prompts are used to provide the user with pre-defined messages that adds context to the LLM.957 * Use the description and title to help the user to understand what the prompt does and when to use it.958 *959 * A prompt can also have a schema that defines the input it expects, the user will be prompted to enter the inputs you request. It can also have a complete function960 * for each input that will be used to provide completions for the user.961 * @template {StandardSchema | undefined} [TSchema=undefined]962 * @overload963 * @param {PromptOptions<TSchema>} prompt_or_options964 * @param {TSchema extends undefined ? (()=>Promise<GetPromptResult> | GetPromptResult) : (input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<GetPromptResult> | GetPromptResult} execute965 * @returns {void}966 * */967 /**968 * Add a prompt to the server. Prompts are used to provide the user with pre-defined messages that adds context to the LLM.969 * Use the description and title to help the user to understand what the prompt does and when to use it.970 *971 * A prompt can also have a schema that defines the input it expects, the user will be prompted to enter the inputs you request. It can also have a complete function972 * for each input that will be used to provide completions for the user.973 * @template {StandardSchema | undefined} [TSchema=undefined]974 * @param {CreatedPrompt<TSchema> | PromptOptions<TSchema>} prompt_or_options975 * @param {TSchema extends undefined ? (()=>Promise<GetPromptResult> | GetPromptResult) : (input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<GetPromptResult> | GetPromptResult} [execute]976 */977 prompt(prompt_or_options, execute) {978 if ('execute' in prompt_or_options) {979 execute = /** @type {NonNullable<typeof execute>} */ (980 prompt_or_options.execute981 );982 }983 if (prompt_or_options.complete) {984 this.#completions['ref/prompt'].set(985 prompt_or_options.name,986 prompt_or_options.complete,987 );988 }989 this.#notify_prompts_list_changed();990 const stored_prompt = /** @type {Prompt<any>} */ (prompt_or_options);991 stored_prompt.execute = /** @type {NonNullable<typeof execute>} */ (992 execute993 );994 this.#prompts.set(prompt_or_options.name, stored_prompt);995 }996 /**997 * @type {(resource: StoredResource & { uri: string })=> void}998 */999 #resource(resource) {1000 if (resource.template && resource.complete) {1001 this.#completions['ref/resource'].set(1002 resource.uri,1003 resource.complete,1004 );1005 }1006 if (resource.template) {1007 this.#templates.add(resource.uri);1008 }1009 this.#notify_resources_list_changed();1010 this.#resources.set(resource.uri, resource);1011 }1012 1013 /**1014 * Add a resource to the server. Resources are added manually to the context by the user to provide the LLM with additional context.1015 * Use the description and title to help the user to understand what the resource is.1016 * @overload1017 * @param {CreatedResource} resource_or_options1018 * @returns {void}1019 */1020 /**1021 * Add a resource to the server. Resources are added manually to the context by the user to provide the LLM with additional context.1022 * Use the description and title to help the user to understand what the resource is.1023 * @overload1024 * @param {ResourceOptions} resource_or_options1025 * @param {(uri: string) => Promise<ReadResourceResult> | ReadResourceResult} execute1026 * @returns {void}1027 */1028 /**1029 * Add a resource to the server. Resources are added manually to the context by the user to provide the LLM with additional context.1030 * Use the description and title to help the user to understand what the resource is.1031 * @param {CreatedResource | ResourceOptions} resource_or_options1032 * @param {(uri: string) => Promise<ReadResourceResult> | ReadResourceResult} [execute]1033 */1034 resource(resource_or_options, execute) {1035 if ('execute' in resource_or_options) {1036 // @ts-expect-error typescript doesn't know about execute because of an egregious hack to prevent it1037 // from showing in intellisense when declaring a tool inline1038 execute = resource_or_options.execute;1039 }1040 const stored_resource =1041 /** @type {StoredResource & { uri: string }} */ (1042 resource_or_options1043 );1044 stored_resource.execute = /** @type {NonNullable<typeof execute>} */ (1045 execute1046 );1047 stored_resource.template = false;1048 this.#resource(stored_resource);1049 }1050 /**1051 * Add a resource template to the server. Resources are added manually to the context by the user to provide the LLM with additional context.1052 * Resource templates are used to create resources dynamically based on a URI template. The URI template should be a valid URI template as defined in RFC 6570.1053 * Resource templates can have a list method that returns a list of resources that match the template and a complete method that returns a list of resources given one of the template variables, this method will1054 * be invoked to provide completions for the template variables to the user.1055 * Use the description and title to help the user to understand what the resource is.1056 * @template {string} TUri1057 * @template {ExtractURITemplateVariables<TUri>} TVariables1058 * @overload1059 * @param {CreatedTemplate<TUri>} template_or_options1060 * @returns {void}1061 */1062 /**1063 * Add a resource template to the server. Resources are added manually to the context by the user to provide the LLM with additional context.1064 * Resource templates are used to create resources dynamically based on a URI template. The URI template should be a valid URI template as defined in RFC 6570.1065 * Resource templates can have a list method that returns a list of resources that match the template and a complete method that returns a list of resources given one of the template variables, this method will1066 * be invoked to provide completions for the template variables to the user.1067 * Use the description and title to help the user to understand what the resource is.1068 * @template {string} TUri1069 * @template {ExtractURITemplateVariables<TUri>} TVariables1070 * @overload1071 * @param {TemplateOptions<TUri>} template_or_options1072 * @param {(uri: string, params: Record<TVariables, string | string[]>) => Promise<ReadResourceResult> | ReadResourceResult} execute1073 * @returns {void}1074 */1075 /**1076 * Add a resource template to the server. Resources are added manually to the context by the user to provide the LLM with additional context.1077 * Resource templates are used to create resources dynamically based on a URI template. The URI template should be a valid URI template as defined in RFC 6570.1078 * Resource templates can have a list method that returns a list of resources that match the template and a complete method that returns a list of resources given one of the template variables, this method will1079 * be invoked to provide completions for the template variables to the user.1080 * Use the description and title to help the user to understand what the resource is.1081 * @template {string} TUri1082 * @template {ExtractURITemplateVariables<TUri>} TVariables1083 * @param {CreatedTemplate<TUri> | TemplateOptions<TUri>} template_or_options1084 * @param {(uri: string, params: Record<TVariables, string | string[]>) => Promise<ReadResourceResult> | ReadResourceResult} [execute]1085 */1086 template(template_or_options, execute) {1087 if ('execute' in template_or_options) {1088 // @ts-expect-error typescript doesn't know about execute because of an egregious hack to prevent it1089 // from showing in intellisense when declaring a tool inline1090 execute = template_or_options.execute;1091 }1092 const stored_template =1093 /** @type {StoredResource & { uri: string }} */ (1094 /** @type {unknown} */ (template_or_options)1095 );1096 stored_template.execute = /** @type {NonNullable<typeof execute>} */ (1097 execute1098 );1099 // @ts-expect-error list_resources only exists on template resources1100 stored_template.list_resources = template_or_options.list;1101 stored_template.template = true;1102 this.#resource(stored_template);1103 }1104 /**1105 * The main function that receive a JSONRpc message and either dispatch a `send` event or process the request.1106 *1107 * @param {JSONRPCMessage} message1108 * @param {Context<CustomContext>} [ctx]1109 * @returns {ReturnType<JSONRPCServer['receive']> | ReturnType<JSONRPCClient['receive'] | undefined>}1110 */1111 receive(message, ctx) {1112 // Validate the message first1113 const validated_message = v.safeParse(1114 v.union([JSONRPCRequestSchema, JSONRPCNotificationSchema]),1115 message,1116 );1117 1118 // Check if it's a request or response1119 if (validated_message.success) {1120 const progress_token = /** @type {string | undefined} */ (1121 validated_message.output.params?._meta?.progressToken1122 );1123 return this.#ctx_storage.run(1124 { ...(ctx ?? {}), progress_token },1125 async () =>1126 await this.#server.receive(validated_message.output),1127 );1128 }1129 // It's a response - handle with client1130 const validated_response = v.parse(1131 v.union([JSONRPCResponseSchema, JSONRPCErrorSchema]),1132 message,1133 );1134 this.#lazyily_create_client();1135 return this.#ctx_storage.run(ctx ?? {}, async () =>1136 this.#client?.receive(validated_response),1137 );1138 }1139 1140 /**1141 * Lower level api to send a request to the client, mostly useful to call client methods that not yet supported by the server or1142 * if you want to send requests with json schema that is not expressible with your validation library.1143 * @param {{ method: string, params?: JSONRPCParams }} request1144 * @returns {Promise<unknown>}1145 */1146 async request({ method, params }) {1147 this.#lazyily_create_client();1148 return this.#client?.request(method, params, 'standalone');1149 }1150 1151 /**1152 * Send a notification for subscriptions1153 * @template {keyof ChangedArgs} TWhat1154 * @param {[what: TWhat, ...ChangedArgs[TWhat]]} args1155 */1156 changed(...args) {1157 const [what, id] = args;1158 if (what === 'prompts') {1159 this.#notify_prompts_list_changed();1160 } else if (what === 'tools') {1161 this.#notify_tools_list_changed();1162 } else if (what === 'resources') {1163 this.#notify_resources_list_changed();1164 } else {1165 const resource = this.#resources.get(id);1166 if (!resource) return;1167 this.#notify(1168 `notifications/resources/updated`,1169 {1170 uri: id,1171 title: resource.name,1172 },1173 'broadcast',1174 );1175 }1176 }1177 1178 /**1179 * Refresh roots list from client1180 */1181 async refreshRoots() {1182 await this.#refresh_roots();1183 }1184 1185 /**1186 * Emit an elicitation request to the client. Elicitations are used to ask the user for input in a structured way, the client will show a UI to the user to fill the input.1187 * The schema should be a valid Standard Schema V1 schema and should be an Object with the properties you need.1188 * The client will return the validated input as a JSON object that matches the schema.1189 *1190 * If the client doesn't support elicitation, it will throw an error.1191 *1192 * @template {StandardSchema extends undefined ? never : StandardSchema} TSchema1193 * @param {string} message1194 * @param {TSchema} schema1195 * @returns {Promise<ElicitResult & { content?: StandardSchemaV1.InferOutput<TSchema> }>}1196 */1197 async elicitation(message, schema) {1198 if (!this.#client_capabilities?.elicitation)1199 throw new McpError(-32601, "Client doesn't support elicitation");1200 