routerFor method
The routing a host hands the runtime as onToolCall: an in-process tool
when there is no client, the full dispatch when there is.
Shared because it is needed at two moments — before initialize, so a
definition-level onInit tool call has somewhere to land (MCP UI DSL
§1.5.2 fires that hook ahead of the first render), and again at
buildUI for everything after. Two copies of it would drift.
scope is the bundle the calls come from, so its own tools answer to the
names it declared (resolveInProcess).
Implementation
Future<dynamic> Function(String, Map<String, dynamic>) routerFor(
Client? client, {
void Function(String tool)? onNoClient,
String? scope,
}) {
return (String tool, Map<String, dynamic> params) async {
if (client == null) {
if (resolveInProcess(tool, scope: scope) != null) {
return _callInProcessForRuntime(tool, params, scope: scope);
}
onNoClient?.call(tool);
// Not `null`: the runtime reads a null return as a successful call
// with no payload, so a misspelled tool name came back as `onSuccess`
// and the document carried on as though the call had happened.
throw ToolExecutionException(
tool,
cause: StateError('no tool named "$tool" and no connected server'),
);
}
return call(client: client, tool: tool, params: params, scope: scope);
};
}