import { Block, type Children, code, For, List, refkey, type Refkey, Show, StatementList } from "@alloy-js/core"; import { ArrayExpression, CaseClause, CommaList, FunctionCallExpression, MemberExpression, ObjectExpression, ObjectProperty, VarDeclaration, } from "@alloy-js/typescript"; import type { Type } from "@typespec/compiler"; import { useTsp } from "@typespec/emitter-framework"; import { useMCPServerContext } from "../context/McpServer.js"; import type { ArrayResultDescriptor, SingleResultDescriptor, ToolDescriptor, } from "../context/utils/tool-descriptor.js"; import { zodValidationError } from "../externals/zod-validation-error.js"; export interface CallToolHandlerProps { tool: ToolDescriptor; args: Refkey; } export function CallToolHandler(props: CallToolHandlerProps) { const { namePolicy, keys: { getToolHandler, toolDispatcher }, } = useMCPServerContext(); const parseResultKey = refkey(); return ( 0}> {props.tool.keys.zodParametersSchema}.safeParse({props.args}); {code` if (!parsed.success) { throw ${zodValidationError.fromZodError}(parsed.error, { prefix: "Request validation error" }); } `} await{" "} {toolDispatcher ? ( ) : ( {props.tool.path.map((x) => ( ))} } args={[...props.tool.parameters.properties.values()].map((p) => { return <>parsed.data.{p.name}; })} /> )} {props.tool.keys.zodReturnSchema}.safeParse(rawResult) {code` if (!maybeResult.success) { throw ${zodValidationError.fromZodError}(maybeResult.error, { prefix: "Response validation error"}); } `} maybeResult.data <> return{" "} , }} /> ); } export interface PackResultProps { tool: ToolDescriptor; parsedResult: Refkey; } /** * Takes the parsed result from a response from the tool implementation * and packs it into the format expected by the MCP server. */ export function MarshalResult(props: PackResultProps) { const result = props.tool.result; if (result.kind === "single") { return ( ); } else if (result.kind === "array") { return ; } } export interface MarshalSingleResultProps { result: SingleResultDescriptor; parsedResult: Children; } export function MarshalSingleResult(props: MarshalSingleResultProps) { const { $ } = useTsp(); if ($.union.is(props.result.resultType)) { const variantTypes = Array.from(props.result.resultType.variants.values()).map((v) => v.type); if (variantTypes.some($.mcp.isKnownMcpResult)) { // special handling of top-level unions-of-known-results // which is to determine which variant was returned and serialize // it accordingly. return ( {(variant, index) => { const check = $.scalar.extendsString(variant) ? ( <>typeof {props.parsedResult} === "string" ) : $.mcp.audioResult.is(variant) ? ( <>{props.parsedResult}.type === "audio" ) : $.mcp.imageResult.is(variant) ? ( <>{props.parsedResult}.type === "image" ) : ( <>false ); return ( <> {index < variantTypes.length - 1 && <>{check} ? } {props.parsedResult} {index < variantTypes.length - 1 && <> : } ); }} ); } } return ( {props.parsedResult} ); } interface MaybeEnvelopeProps { resultType: Type; children: Children; } function MaybeResultEnvelope(props: MaybeEnvelopeProps) { const { $ } = useTsp(); // When we have a single result, the only thing that doesn't need an envelope // added is a single audio or image result. Everything else will come in from // business logic as the naked type and needs to be packed into a results // envelope. const needsEnvelope = !$.mcp.audioResult.is(props.resultType) && !$.mcp.imageResult.is(props.resultType); if (needsEnvelope) { return ( {props.children} ); } else { return <>{props.children}; } } interface MaybeSerializeProps { resultType: Type; children: Children; } function MaybeSerialize(props: MaybeSerializeProps) { const { $ } = useTsp(); const resultType = props.resultType; if ($.scalar.extendsString(resultType)) { // nothing to do for strings return <>{props.children}; } else if ($.mcp.audioResult.is(resultType) || $.mcp.imageResult.is(resultType)) { // todo: embedded resource return ( <>type: {props.children}.type <>mimeType: {props.children}.mimeType <> data: {props.children}.data]} /> .toString("base64") ); } else if ($.scalar.extendsBytes(resultType)) { // todo: embedded resource return ( <> .toString("base64") ); } else { // serialize the rest as JSON return ; } } export interface MarshalArrayResultProps { result: ArrayResultDescriptor; parsedResult: Refkey; } // todo: update with function expression function MarshalArrayResult(props: MarshalArrayResultProps) { return ( <> {props.parsedResult}.map((item) =>{" "} return ) ); }