MCPcopy Create free account
hub / github.com/TanStack/ai / createMcpAppCallHandler

Function createMcpAppCallHandler

packages/ai-mcp/src/apps/call-handler.ts:189–283  ·  view source on GitHub ↗
(opts: McpAppCallHandlerOptions)

Source from the content-addressed store, hash-verified

187 * Always closes the per-call client in `finally`. Never returns transport config.
188 */
189export function createMcpAppCallHandler(opts: McpAppCallHandlerOptions) {
190 const registry = buildRegistry(opts.clients)
191
192 // Resolve a serverId against the static `clients` registry. When serverId is
193 // undefined and exactly one descriptor is registered, default to that sole
194 // descriptor; with zero or multiple, undefined stays unresolvable.
195 const resolveFromRegistry = (
196 serverId: string | undefined,
197 ): McpServerDescriptor | null => {
198 if (serverId !== undefined) {
199 return registry.byServerId[serverId] ?? null
200 }
201 if (registry.total !== 1) return null
202 return registry.fallback ?? Object.values(registry.byServerId)[0] ?? null
203 }
204
205 return async (
206 req: McpAppCallRequest,
207 ): Promise<{ ok: true; result: unknown } | { ok: false; error: string }> => {
208 // Resolve server descriptor. The store WINS when it has an entry; otherwise
209 // we fall back to the static `clients` registry (the base). A store miss
210 // (null) must not reject when the registry can serve the request.
211 const descriptor =
212 (opts.store ? await opts.store.get(req.threadId, req.serverId) : null) ??
213 resolveFromRegistry(req.serverId)
214
215 if (!descriptor) {
216 // serverId omitted but resolution was ambiguous (zero or multiple
217 // servers configured) → clearer message than "Unknown serverId: undefined".
218 const error =
219 req.serverId === undefined
220 ? 'No serverId provided and zero or multiple servers configured; specify serverId'
221 : `Unknown serverId: ${req.serverId}`
222 return { ok: false, error }
223 }
224
225 if (descriptor.transport === undefined) {
226 // Client was built from a raw Transport instance (no reconnectable
227 // descriptor), so there is nothing to reconnect per-call.
228 return {
229 ok: false,
230 error: 'MCP client has no reconnectable transport descriptor',
231 }
232 }
233
234 const client = await createMCPClient({
235 transport: descriptor.transport,
236 prefix: descriptor.prefix,
237 })
238
239 try {
240 // The widget sends the server-native (UNPREFIXED) tool name
241 // (`UIResourcePart.toolName` is the native name), so we match it directly
242 // against the native names the server exposes — carried on
243 // `metadata.mcp.serverToolName` (falling back to `name` for unprefixed
244 // clients) — and forward `req.toolName` unchanged to `client.callTool`.
245 const exposedNative = new Set(
246 (await client.tools()).map((t) => serverToolNameOf(t)),

Callers 4

weatherPoolHandlerFunction · 0.90

Calls 11

createMCPClientFunction · 0.90
buildRegistryFunction · 0.85
resolveFromRegistryFunction · 0.85
serverToolNameOfFunction · 0.85
isArgsRecordFunction · 0.85
reportErrorFunction · 0.85
hasMethod · 0.80
callToolMethod · 0.80
getMethod · 0.65
closeMethod · 0.65
toolsMethod · 0.45

Tested by 1

weatherPoolHandlerFunction · 0.72