UNPKG

@nestia/core

Version:

Super-fast validation decorators of NestJS

156 lines (149 loc) 5.18 kB
import { IRequestBodyValidator } from "../options/IRequestBodyValidator"; import { IMcpRouteReflect } from "./internal/IMcpRouteReflect"; import { validate_request_body } from "./internal/validate_request_body"; /** * MCP (Model Context Protocol) route decorator. * * `@McpRoute()` marks a controller method as a callable MCP tool. When the * application bootstraps, every method annotated with this decorator is * registered on the MCP server built by {@link McpAdaptor.upgrade}, making it * reachable by LLM clients through the standard Streamable HTTP transport. * * The public form takes only the tool's `name` (string). Human-readable * `description` and `title` are read from the method's JSDoc: * * - `description`: the JSDoc comment body. * - `title`: the value of an optional `@title` JSDoc tag. * * For type-safe tool inputs, decorate exactly one parameter of the method with * {@link McpRoute.Params}. The parameter type `T` is analyzed at compile time by * the nestia transformer, which generates both a runtime validator and the JSON * Schema attached to `inputSchema` in `tools/list` responses. * * For the MCP endpoint to actually be served, call {@link McpAdaptor.upgrade} on * the {@link INestApplication} instance at bootstrap. The decorator alone only * stores reflection metadata. * * @author wildduck - https://github.com/wildduck2 * @example * ```typescript * import core from "@nestia/core"; * * @Controller() * export class WeatherController { * /** * * Return current weather for a city. * * * * @title Get weather * *\/ * @core.McpRoute("get_weather") * public async get( * @core.McpRoute.Params() params: { city: string }, * ): Promise<{ temp: number }> { * return { temp: 22 }; * } * } * ```; * * @param name Unique tool identifier exposed to MCP clients via `tools/list`. * @returns Method decorator. */ export function McpRoute(name: string): MethodDecorator; /** @internal */ export function McpRoute(config: McpRoute.IConfig): MethodDecorator; export function McpRoute(input: string | McpRoute.IConfig): MethodDecorator { const config: McpRoute.IConfig = typeof input === "string" ? { name: input } : input; return function McpRoute( _target: Object, _propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>, ): TypedPropertyDescriptor<any> { Reflect.defineMetadata( "nestia/McpRoute", { name: config.name, title: config.title, description: config.description, inputSchema: config.inputSchema ?? { type: "object", properties: {} }, outputSchema: config.outputSchema, annotations: config.annotations, } satisfies IMcpRouteReflect, descriptor.value, ); return descriptor; }; } export namespace McpRoute { /** * Configuration object emitted by the nestia transformer at compile time. * * Users call `@McpRoute("name")`; the transformer rewrites the call to * `@McpRoute({ name, description, title, inputSchema, ... })` after parsing * method JSDoc and analyzing the `@McpRoute.Params<T>()` parameter type. * * @internal */ export interface IConfig { name: string; title?: string; description?: string; inputSchema?: object; outputSchema?: object; annotations?: IMcpRouteReflect["annotations"]; } /** * Parameter decorator for an MCP tool's input arguments. * * `@McpRoute.Params<T>()` validates the `arguments` object from a * `tools/call` request against the TypeScript type `T` using typia. A failed * validation surfaces to the client as a JSON-RPC `-32602` (`InvalidParams`) * error with structured diagnostics, giving the LLM precise feedback to * self-correct. * * MCP tools accept exactly one arguments object; applying this decorator more * than once on a single method is a compile-time error. The decorated type * `T` must be an object type without dynamic properties. * * @author wildduck - https://github.com/wildduck2 * @param validator Optional custom validator. Default is `typia.assert()`. * @returns Parameter decorator. */ export function Params<T>( validator?: IRequestBodyValidator<T>, ): ParameterDecorator { const validate = validate_request_body("McpRoute.Params")(validator); return function McpRouteParams( target: Object, propertyKey: string | symbol | undefined, parameterIndex: number, ) { emplace(target, propertyKey ?? "", { category: "params", index: parameterIndex, validate, }); }; } /** @internal */ const emplace = ( target: Object, propertyKey: string | symbol, value: IMcpRouteReflect.IArgument, ) => { const array: IMcpRouteReflect.IArgument[] | undefined = Reflect.getOwnMetadata( "nestia/McpRoute/Parameters", target, propertyKey, ); if (array !== undefined) array.push(value); else Reflect.defineMetadata( "nestia/McpRoute/Parameters", [value], target, propertyKey, ); }; }