addTool method

Registration addTool(
  1. ToolSpec tool,
  2. ProjectToolRun run, {
  3. String? namespace,
})

Adds tool, to be run by run, published as <namespace>.<name>.

Through a plugin's view the namespace is the plugin's (PluginManifest.namespace), and namespace must be left out or be that one. Through the root the application names one; the root refuses a tool without it.

Throws an ArgumentError for a name that is not letters, digits, _ and -, for a published name longer than maxNameLength, and for one already added — naming who added it. Throws a PluginException when a plugin whose manifest does not list PluginPermission.tools adds one, and when a plugin names another namespace than its own.

Implementation

Registration addTool(ToolSpec tool, ProjectToolRun run, {String? namespace}) {
  final scope = _scope;
  final String space;
  if (scope != null) {
    final manifest = scope.manifest;
    final id = manifest.id;
    if (!manifest.permissions.contains(PluginPermission.tools)) {
      throw PluginException(
        'plugin "$id" adds the MCP tool "${tool.name}" without asking for '
        'the `tools` permission: list PluginPermission.tools in its '
        'manifest, so whoever installs it sees that it talks to agents',
      );
    }
    space = manifest.namespace;
    if (namespace != null && namespace != space) {
      // Refused as the plugin's other names outside its namespace are,
      // naming the tool as it would have been published.
      throw PluginException(
        manifest.nameProblem(
              '$namespace.${tool.name}',
              what: 'the MCP tool',
            ) ??
            'plugin "$id" adds its tools under its namespace, "$space", '
                'not "$namespace"',
      );
    }
  } else {
    if (namespace == null) {
      throw ArgumentError.value(
        tool.name,
        'tool',
        'the application names the namespace its tools go under, its own '
            'name: addTool(tool, run, namespace: \'acme_racer\')',
      );
    }
    if (!_namespacePattern.hasMatch(namespace)) {
      throw ArgumentError.value(
        namespace,
        'namespace',
        'a namespace is lower case letters, digits, "_", "." and "-", '
            'starting with a letter, as a plugin id is',
      );
    }
    // The servers' own tools are offered beside the application's, so an
    // area of theirs would shadow its tools, now or in a minor.
    if (PluginManifest.reservedAreas.contains(namespace) ||
        namespace == PluginManifest.reservedNamespace) {
      throw ArgumentError.value(
        namespace,
        'namespace',
        'names one of the engine\'s own areas, whose tools would shadow '
            'the application\'s; name the tools after the application '
            '(\'acme_racer\')',
      );
    }
    space = namespace;
  }
  if (!_namePattern.hasMatch(tool.name)) {
    throw ArgumentError.value(
      tool.name,
      'tool',
      'a tool\'s name is letters, digits, "_" and "-"; the namespace '
          'and its dot are added for it',
    );
  }
  final name = published(space, tool.name);
  if (name.length > maxNameLength) {
    throw ArgumentError.value(
      name,
      'tool',
      'is ${name.length} characters published; at most $maxNameLength',
    );
  }
  final owner = scope == null
      ? 'the application'
      : 'plugin "${scope.manifest.id}"';
  if (named(name) case final ProjectTool taken) {
    throw ArgumentError.value(
      name,
      'tool',
      'is already added, by ${taken.owner}; $owner cannot add it again',
    );
  }
  final added = ProjectTool._(
    tool.copyWith(
      name: name,
      meta: <String, Object?>{
        'flutter3d/schemaVersion': ?_store.schemaVersions[space],
      },
    ),
    run,
    namespace: space,
    owner: owner,
    scope: scope,
    sequence: _store.sequence++,
  );
  _store.tools.add(added);
  _store.changed();
  // By sequence rather than identity: a version declared later replaces
  // the entry with one carrying it (see [declareSchemaVersion]).
  final registration = Registration(() {
    final before = _store.tools.length;
    _store.tools.removeWhere(
      (ProjectTool t) => t._sequence == added._sequence,
    );
    if (_store.tools.length != before) _store.changed();
  });
  scope?.track(registration);
  return registration;
}