11 KiB
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.gowithAgentTools []AgentTool,AgentToolAnnotations, surface constants, and the exact YAML/JSON fields from the spec. - Extend
apps/backend/internal/plugins/manifest/validate.gowith per-plugin count, name, surface, description, exposed-name length, annotation, schema size, object-root, and JSON Schema compilation checks. Extract the reusable compiler frominternal/mcp/server/tool_argument_validation.gointointernal/mcp/toolschemaso install-time and invocation-time validation have one interpretation. - Derive readable exposed names in
internal/mcp/plugintoolsfrom 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.gofor the normalized runtime descriptor and catalog snapshot (generation,revision, sorted tools). Keep this package independent of plugin storage and agentctl. - Add
InvokeAgentToolplus request, bound context, and result messages toapps/backend/proto/kandev/plugin/v1/plugin.proto; regenerate protobuf and gRPC stubs withmake -C apps/backend proto. - Extend
apps/backend/pkg/pluginsdk/plugin.go,types.go, andserve.gowith an optionalAgentToolPlugininterface. Keep the basePlugininterface unchanged, make the gRPC adapter returnUnimplementedwhen the optional interface is absent, and expose a thin context-respectingRemotePluginclient.
Authoritative catalog and invocation
- Add catalog derivation to
apps/backend/internal/plugins/service.go(or a focusedagent_tools.go): return a canonical snapshot from installed records, include onlyStatusActive, 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)besideInvokeWebhook. Re-read the current record, requireStatusActive, find the exact declaration, validate surface/input, enforce the 30-second deadline and 1 MiB result limit, callRemotePlugin.InvokeAgentToolonce, 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.Contextwith the canonical plugin-tool snapshot; normalization deep-copies and sorts descriptors and includes them in profile equality. - Add
Server.SetPluginTools(snapshot)and incorporate aplugin-toolsgroup intoprofileToolGroups. Filter definitions tokanban-taskandoffice-task, construct tools withmcp.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_toolWebSocket action. It maps the backend's fallback text, structured content, andis_errorresponse to onemcp.CallToolResult. - Add
PUT /api/v1/mcp/plugin-toolsand the matching runtime agentctl client. Accept a new generation, ignore stale same-generation revisions, skip equivalent effective registries, and rely on oneSetToolscall 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
ActionMCPInvokePluginTooltoapps/backend/pkg/websocket/actions.goand register a handler inapps/backend/internal/mcp/handlerswith 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/scopeidentity 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.resolveTaskSessionMCPProfilebuilds 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/backendapporinternal/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, andpkg/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
18101has 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.