dispatch method

  1. @override
Future<Object?> dispatch(
  1. String verb,
  2. List<Object?> args
)
override

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');
  }
}