Files
2026-08-24 11:09:03 +00:00

11 KiB

spec, created, status
spec created status
docs/specs/plugins/requirements/agent-tools.md 2026-08-11 in_progress

Implementation Plan: Plugin-Contributed Agent Tools

Overview

Add a bounded manifest and SDK contract first, then implement the authoritative plugin catalog and gRPC invocation path. Extend the typed MCP profile with dynamic plugin descriptors, teach agentctl to atomically register proxy tools, and wire launch plus live catalog refresh through the existing runtime. Finish with an end-to-end managed-plugin fixture and public authoring documentation.


Backend

Manifest, transport, and SDK contracts

  • Extend apps/backend/internal/plugins/manifest/manifest.go with AgentTools []AgentTool, AgentToolAnnotations, surface constants, and the exact YAML/JSON fields from the spec.
  • Extend apps/backend/internal/plugins/manifest/validate.go with per-plugin count, name, surface, description, exposed-name length, annotation, schema size, object-root, and JSON Schema compilation checks. Extract the reusable compiler from internal/mcp/server/tool_argument_validation.go into internal/mcp/toolschema so install-time and invocation-time validation have one interpretation.
  • Derive readable exposed names in internal/mcp/plugintools from a slugged plugin ID plus the validated local name. Keep the complete canonical name compatible with restrictive agent tool-name grammars, using a short stable hash suffix only when the readable form exceeds the length limit.
  • Add apps/backend/internal/mcp/plugintools/types.go for the normalized runtime descriptor and catalog snapshot (generation, revision, sorted tools). Keep this package independent of plugin storage and agentctl.
  • Add InvokeAgentTool plus request, bound context, and result messages to apps/backend/proto/kandev/plugin/v1/plugin.proto; regenerate protobuf and gRPC stubs with make -C apps/backend proto.
  • Extend apps/backend/pkg/pluginsdk/plugin.go, types.go, and serve.go with an optional AgentToolPlugin interface. Keep the base Plugin interface unchanged, make the gRPC adapter return Unimplemented when the optional interface is absent, and expose a thin context-respecting RemotePlugin client.

Authoritative catalog and invocation

  • Add catalog derivation to apps/backend/internal/plugins/service.go (or a focused agent_tools.go): return a canonical snapshot from installed records, include only StatusActive, reserve names across installed declarations, and increment a process-generation/revision identity only when effective content changes.
  • Validate cross-plugin exposed-name collisions before install/upgrade commits in the existing service transaction path so a failed replacement preserves the previous version and catalog.
  • Add Service.InvokeAgentTool(ctx, pluginID, localName, arguments, boundContext) beside InvokeWebhook. Re-read the current record, require StatusActive, find the exact declaration, validate surface/input, enforce the 30-second deadline and 1 MiB result limit, call RemotePlugin.InvokeAgentTool once, and validate output before returning a transport-neutral result.
  • Publish a nonblocking catalog-change signal after install, upgrade, enable, disable, error, recovery, and uninstall persistence succeeds. Do not invoke refresh callbacks while the plugin service mutex or runtime supervision callback is blocked.
  • Add structured invocation logs containing identifiers, duration, and outcome only; never log arguments or results.

Dynamic MCP registry in agentctl

  • Extend internal/mcp/profile.Context with the canonical plugin-tool snapshot; normalization deep-copies and sorts descriptors and includes them in profile equality.
  • Add Server.SetPluginTools(snapshot) and incorporate a plugin-tools group into profileToolGroups. Filter definitions to kanban-task and office-task, construct tools with mcp.NewToolWithRawSchema, attach output schema and annotations, and bind each handler closure to plugin ID/local name.
  • The dynamic handler forwards only arguments plus server-bound task/session identity through a new mcp.invoke_plugin_tool WebSocket action. It maps the backend's fallback text, structured content, and is_error response to one mcp.CallToolResult.
  • Add PUT /api/v1/mcp/plugin-tools and the matching runtime agentctl client. Accept a new generation, ignore stale same-generation revisions, skip equivalent effective registries, and rely on one SetTools call to emit one list-changed notification.
  • Preserve existing shared top-level argument validation for dynamic schemas. A malformed runtime descriptor must fail closed without replacing the last valid registry.

Backend dispatch and lifecycle propagation

  • Add ActionMCPInvokePluginTool to apps/backend/pkg/websocket/actions.go and register a handler in apps/backend/internal/mcp/handlers with a narrow plugin-tool service interface.
  • The handler takes plugin/tool identity from the agentctl closure, task/session identity from the bound MCP server payload, and workspace/surface from persisted backend state. Apply mcp/scope identity and task-session pair authorization before plugin lookup; repeat schema and declaration checks in the plugin service.
  • Inject the current catalog snapshot when orchestrator/executor.resolveTaskSessionMCPProfile builds launch/resume profiles. Keep configuration and external surfaces empty.
  • Add a lifecycle method that replaces plugin tools on every live agentctl execution. Wire a backendapp catalog refresher/coalescer to the plugin service change signal; serialize snapshots, continue past per-execution failures, and leave persisted plugin state authoritative.
  • Ensure launch/resume and the next live change repair a failed refresh. Do not restart an agent solely because a client may ignore list-changed.

Managed fixture and full-path proof

  • Extend the runtime fixture plugin to declare and implement an echo/context agent tool and a controlled error/timeout case.
  • Add an integration test that starts the managed fixture, creates the task MCP server over the real backend request bridge, lists the dynamic tool, invokes it, and verifies bound context and result mapping.
  • Cover live enable/disable or active/error/recovery replacement with an initialized MCP client and assert exactly one list-changed notification per effective change, no notification for a stale/equivalent snapshot, and fail-closed invocation after removal.

Tests

  • What: Manifest limits, surface rules, schema compilation, conservative annotation defaults, deterministic names, and collision rejection. File: internal/plugins/manifest/manifest_test.go, internal/plugins/service_test.go. How: table-driven unit tests plus a failed-upgrade preservation test.
  • What: SDK/protobuf invocation conversion and source-compatible UnimplementedPlugin. File: pkg/pluginsdk/plugin_test.go, pkg/pluginsdk/serve_test.go. How: in-memory go-plugin gRPC round trip.
  • What: Active-only canonical catalog revisions and bounded, non-retried invocation with schema/result enforcement. File: internal/plugins/agent_tools_test.go. How: fake runtime plus controlled remote plugin tests.
  • What: Surface-filtered atomic registry replacement, annotations, stale revision rejection, schema enforcement, and one list-changed notification. File: internal/mcp/server/plugin_tools_test.go. How: initialized mcp-go session with a recording backend client.
  • What: Agentctl live control transport preserves built-in surface, capabilities, and providers while replacing plugin descriptors. File: internal/agentctl/server/api/mcp_plugin_tools_test.go, internal/agent/runtime/agentctl/client_mcp_plugin_tools_test.go. How: HTTP handler/client contract tests.
  • What: Backend invocation rejects stale discovery, mismatched task/session, unsupported surface, and invalid input before entering the plugin. File: internal/mcp/handlers/plugin_tools_test.go. How: scoped dispatcher tests with a recording plugin-tool service.
  • What: Launch/resume carry the complete snapshot and live changes reach all active executions without one failure blocking others. File: internal/orchestrator/executor/executor_mcp_mode_test.go, internal/agent/runtime/lifecycle/manager_mcp_plugin_tools_test.go, internal/backendapp/plugin_tool_refresher_test.go. How: resolver, lifecycle client, and coalescing refresher tests.
  • What: Managed plugin to MCP end-to-end discovery, invocation, bound context, output mapping, and hot removal. File: a focused integration test under internal/backendapp or internal/plugins/runtime. How: real fixture subprocess and MCP JSON-RPC transport with bounded timeouts and teardown.

Verification Results

  • Implemented and verified manifest/SDK, plugin catalog/invocation, dynamic MCP registry, backend dispatch, and the managed fixture package.
  • Focused race suites pass for internal/plugins, internal/mcp/server, internal/mcp/handlers, internal/agent/runtime/lifecycle, internal/agent/runtime/agentctl, internal/agentctl/server/api, internal/backendapp, and pkg/pluginsdk.
  • go test ./... reaches unrelated existing filesystem/task-service failures in this environment; no plugin/MCP package failures were reported.
  • A live backend on port 18101 has the fixture package installed and active.

Implementation Waves And Parallel Candidates

Wave 1:

Wave 2 (parallel candidates after Task 01; user authorization required):

Wave 3:

Wave 4:

Wave 5:

The default implementation order is sequential in the primary conversation. Wave labels do not authorize subagents.

Open Risks

  • MCP clients vary in whether they honor tools/list_changed; conformance must be checked against supported clients, but backend authorization cannot depend on refresh behavior.
  • Plugin lifecycle callbacks run on supervision paths that must not block. Catalog propagation needs a serialized, nonblocking coordinator.
  • The public manifest/protobuf/SDK contract is additive but durable. Generated stubs, authoring docs, fixture packages, and minimum Kandev version guidance must land together.