dispatch method
Dispatch a verb invocation. Returns any JSON-serialisable value (or a Future thereof) — the bridge JSON-encodes and hands back to the JS Promise. Throw to reject the promise.
args is the list passed by JS (host.fs.read(path) → [path]).
Atoms validate arity / types and throw ArgumentError for schema
violations so the JS caller gets a clean error message.
Implementation
@override
Future<Object?> dispatch(String verb, List<Object?> args) async {
switch (verb) {
case 'callTool':
if (args.isEmpty) {
throw ArgumentError('callTool requires (toolName, [args])');
}
final toolName = args[0];
if (toolName is! String || toolName.isEmpty) {
throw ArgumentError('toolName must be a non-empty String');
}
final toolArgs =
args.length > 1 ? args[1] : const <String, dynamic>{};
if (toolArgs is! Map) {
throw ArgumentError('args must be an object map');
}
final params = Map<String, dynamic>.from(toolArgs);
try {
final b = bridge;
final s = session;
final result = (b != null && s != null)
? await b.runScoped(s,
() => _dispatcher.callInProcess(toolName, params))
: await _dispatcher.callInProcess(toolName, params);
// The in-process result is already a decoded Map / List /
// primitive (the shape returned by `ToolDispatcher.callInProcess`).
// Wrap it in the JS-side `{isError, body}` envelope contract.
return <String, dynamic>{
'isError': false,
'body': result,
};
} catch (e) {
return <String, dynamic>{
'isError': true,
'body': <String, dynamic>{'error': e.toString()},
};
}
case 'listTools':
return _dispatcher.inProcessToolNames;
default:
throw ArgumentError('unknown verb: mcp.$verb');
}
}