search ESC

Searching…

No results for "".

Type at least 2 characters to search.

Docs

Plugin Authoring Guide

Overview

A plugin is a Dart package that ships an ArtisanServiceProvider subclass. The subclass declares the CLI commands and MCP tool descriptors the plugin contributes. When a consumer calls plugin:install , artisan registers the provider in lib/app/_plugins.g.dart, which is loaded at startup and merged into the ArtisanRegistry. Commands appear in artisan list; MCP tool descriptors surface through the stdio JSON-RPC server so LLM agents can invoke them by name.


Getting Started

Scaffold the plugin skeleton with the make:plugin command:

dart run fluttersdk_artisan make:plugin awesome_plugin

The generator creates the following tree inside awesome_plugin/:

awesome_plugin/
  lib/
    awesome_plugin.dart          # barrel export
    src/
      awesome_artisan_provider.dart   # ArtisanServiceProvider subclass
      commands/                       # one file per ArtisanCommand
  pubspec.yaml                   # declares fluttersdk_artisan dependency
  CHANGELOG.md
  README.md

The generated provider is immediately wirable: plugin:install awesome_plugin injects AwesomeArtisanProvider.new into the consumer's _plugins.g.dart.


ArtisanServiceProvider Contract

Every plugin must extend ArtisanServiceProvider (lib/src/console/artisan_service_provider.dart:11-25):

abstract class ArtisanServiceProvider {
  /// Human-readable provider name used in collision error messages.
  /// Defaults to the runtime class name.
  String get providerName => runtimeType.toString();

  /// Returns the commands this provider contributes to the application.
  List commands();

  /// Returns the MCP tool descriptors this provider contributes.
  ///
  /// Defaults to an empty list so existing providers compile without
  /// modification. Override to expose VM-Service-backed tools to the MCP
  /// server.
  List mcpTools() => const [];
}

Key points:

  • commands() is abstract: return every ArtisanCommand subclass your plugin ships. Duplicate command names throw ArtisanCommandCollisionException.
  • mcpTools() defaults to an empty list. Override it to expose VM Service extension tools to the MCP server.
  • providerName defaults to runtimeType.toString(). Override it with a stable identifier (e.g. 'fluttersdk_dusk') so collision messages survive class renames.
  • There is no register() lifecycle hook on ArtisanServiceProvider. Initialization belongs in command constructors or the host app's boot phase.

Minimal scaffold provider (assets/stubs/make_plugin/generic/provider.dart.stub):

import 'package:fluttersdk_artisan/artisan.dart';

final class AwesomeArtisanProvider extends ArtisanServiceProvider {
  @override
  List commands() => [
        // Commands added via `make:command` land here automatically.
      ];
  // To expose MCP tools, override mcpTools() returning your descriptor list.
}

Writing Commands

Each entry in commands() is an ArtisanCommand subclass. Declare the command surface via the Signature DSL or configure(ArgParser):

  • Signature DSL (preferred): String get signature => 'cmd:name {arg} {--flag}'. See Signature DSL reference for the full syntax.
  • configure(ArgParser): explicit fallback for cases the DSL cannot express (subcommands, allowed values with validation, etc.).

Scaffold a new command inside the plugin directory:

dart run fluttersdk_artisan make:command MyVerb

The generator writes lib/src/commands/my_verb_command.dart and auto-registers it in the provider's commands() list. See make:command for the full scaffold walkthrough.


Adding MCP Tools

Override mcpTools() and return a List. Each descriptor (lib/src/mcp/mcp_tool_descriptor.dart:24-82) is a const-constructible value with four required fields:

Field Type Purpose
name String Snake-case, service-prefixed tool name (e.g. awesome_start). Must be globally unique across all loaded providers.
description String Action-oriented, LLM-targeted description. Imperative opening sentence, usage bullets, constraint-forward language.
inputSchema Map JSON Schema draft-7 object describing accepted input. Pass {'type': 'object', 'properties': {}} when the tool takes no arguments.
extensionMethod String VM Service extension registered in the running Flutter isolate (e.g. ext.awesome.start). Internal routing key; not included in the MCP wire shape.

Example provider with commands and MCP tools:

import 'package:fluttersdk_artisan/artisan.dart';

final class AwesomePluginArtisanProvider extends ArtisanServiceProvider {
  @override
  List commands() => [
        AwesomeStartCommand(),
        AwesomeStopCommand(),
      ];

  @override
  List mcpTools() => [
        McpToolDescriptor(
          name: 'awesome_start',
          description: 'Start the awesome service',
          inputSchema: {'type': 'object', 'properties': {}},
        ),
      ];
}

The example above omits extensionMethod for brevity. In production all four fields are required; extensionMethod must match the registerExtension call in the running Flutter isolate (e.g. 'ext.awesome.start').

The MCP server collects descriptors from all loaded providers when runArtisan(collectMcpTools: true) fires (bin/mcp.dart sets this automatically). Tool names must be unique; a collision throws ArtisanMcpToolCollisionException at startup.

Naming convention: prefix every tool name with a short service identifier followed by an underscore (for example my_plugin_greet). The prefix should match the package's providerName (or a recognizable abbreviation thereof).


Publishing

  1. Bump the version in pubspec.yaml following SemVer (patch for fixes, minor for new commands/tools, major for breaking provider API changes).

  2. Declare the dependency on fluttersdk_artisan with a caret constraint, not path::

    dependencies:
      fluttersdk_artisan: ^0.0.1
    
  3. Validate the archive before tagging:

    dart pub publish --dry-run
    
  4. Add a CHANGELOG entry, then publish:

    dart pub publish
    

Linking from Consumer

The consumer installs the plugin with:

dart run fluttersdk_artisan plugin:install awesome_plugin

This registers AwesomeArtisanProvider.new in lib/app/_plugins.g.dart and re-generates the barrel; the provider is discovered at the next artisan start. See plugin:install for the three routing modes (manifest, canonical scaffold, legacy).


  • install-yaml - install.yaml manifest schema for declaring install steps declaratively.
  • installer-dsl - PluginInstaller fluent DSL for authoring programmatic install logic.
  • make:plugin - scaffold command reference with all flags and the generated file tree.