Files
kandev/docs/specs/plugins/agent-tools.md
T
2026-08-12 20:25:13 +01:00

267 lines
12 KiB
Markdown

---
status: draft
created: 2026-08-11
owner: Kandev
---
# Plugin-Contributed Agent Tools
## Why
Plugin authors can extend Kandev's UI and data behavior, but task agents cannot
invoke plugin-owned operations. Authors currently have to ship and configure a
separate MCP server, which duplicates plugin installation, lifecycle, trust,
and task-context handling. Kandev needs one supported way for an installed
plugin to contribute task-aware tools to agents.
Decision: [ADR-2026-08-11-plugin-tools-through-kandev-mcp](../../decisions/2026-08-11-plugin-tools-through-kandev-mcp.md).
Implementation plan: [Plugin-Contributed Agent Tools](../../plans/plugin-agent-tools/plan.md).
## What
- A managed plugin package (`runtime.type: binary`) may declare zero or more
agent tools in its manifest. Each declaration has a plugin-local name,
description, input JSON Schema, optional output JSON Schema, supported task
surfaces, and optional MCP annotations. Legacy remote HTTP plugins cannot
declare agent tools because they do not implement the managed gRPC runtime.
- Only active plugins contribute tools. Registered, disabled, errored, and
uninstalled plugins contribute none.
- Plugin tools are exposed through the existing task-aware Kandev MCP server.
Plugins do not run or register a second MCP server.
- Kandev derives a readable provider-safe MCP name as
`kandev_<plugin-id-slug>_<local-name>`, where the plugin ID slug replaces
punctuation with underscores. The manifest does not choose an arbitrary
global MCP name. If the readable name exceeds the provider limit, Kandev
truncates the slug and appends a short stable hash suffix. Install
validation still rejects a final-name collision.
- A declared tool may target `kanban-task`, `office-task`, or both. Plugin tools
are not exposed to `configuration` or `external` MCP surfaces in this version.
- Installing, upgrading, enabling, disabling, degrading, recovering, or
uninstalling a plugin recomputes the authoritative plugin-tool catalog.
- Running MCP servers replace their effective tool registry atomically. A
changed registry emits one `notifications/tools/list_changed` notification;
the agent process, task session, and MCP server do not restart.
- A client that does not honor `tools/list_changed` sees the new catalog after
its next MCP reconnect or task-session restart. Kandev does not terminate a
healthy agent process solely to force discovery refresh.
- Tool invocation is a single request to the already supervised plugin process.
Kandev supplies immutable invocation ID, task ID, session ID, workspace ID,
and MCP surface context derived from the running session. Those values are not
accepted from tool arguments.
- The plugin returns required fallback text, optional structured JSON content,
and an `is_error` value. Structured content must conform to the declared
output schema when one is present.
- Tool calls have a 30-second deadline, propagate cancellation, are never
automatically retried, and return at most 1 MiB across fallback text and
structured content.
- Manifest validation limits each plugin to 16 tools, local names to 32
characters, descriptions to 1,024 bytes, and each serialized schema to
64 KiB. Input and output schemas must compile and have an object root.
- MCP annotations are hints, not authorization. Omitted annotations use
conservative defaults: `readOnlyHint=false`, `destructiveHint=true`,
`idempotentHint=false`, and `openWorldHint=true`.
- Declaring an agent tool grants only discovery and invocation of that RPC. It
grants no additional Host API capability; existing `api_read`, `api_write`,
`state`, `secrets`, `agent_invoke`, and `auth` gates remain authoritative.
- Discovery is not an authorization boundary. Every invocation revalidates the
active plugin record, exact declared tool, allowed surface, backend-bound
task/session pair, caller scope, and input schema before calling the plugin.
- Plugin tool calls are recorded in structured logs with invocation ID, plugin
ID, local tool name, task ID, session ID, duration, and outcome. Arguments,
structured output, secret values, and plugin configuration are not logged.
## Manifest Contract
```yaml
agent_tools:
- name: add_tag
description: Add an existing tag to the current task.
surfaces: [kanban-task]
input_schema:
type: object
properties:
tag_id:
type: string
required: [tag_id]
additionalProperties: false
output_schema:
type: object
properties:
task_id:
type: string
tag_id:
type: string
required: [task_id, tag_id]
additionalProperties: false
annotations:
read_only_hint: false
destructive_hint: false
idempotent_hint: true
open_world_hint: false
```
`name` must match `^[a-z0-9][a-z0-9_]{0,31}$`. `surfaces` must contain one
or both supported task surfaces without duplicates. `input_schema` is required;
`output_schema` and `annotations` are optional. Unknown fields continue to
follow the manifest's existing compatibility rules.
An install or upgrade fails before changing the active installation when its
declarations are invalid, when two declarations produce the same exposed name,
or when a declaration conflicts with a tool name reserved by another installed
plugin version. A failed package change leaves the previous installed version
and its tools unchanged.
## Plugin SDK Contract
Plugins that contribute declared tools implement the optional `AgentToolPlugin`
interface:
```go
type AgentToolPlugin interface {
InvokeAgentTool(context.Context, *AgentToolRequest) (*AgentToolResult, error)
}
```
The author-facing types are:
```go
type AgentToolRequest struct {
InvocationID string
Name string
Arguments map[string]any
Context AgentToolContext
}
type AgentToolContext struct {
TaskID string
SessionID string
WorkspaceID string
Surface string
}
type AgentToolResult struct {
Text string
StructuredContent map[string]any
IsError bool
}
```
The base `Plugin` interface remains unchanged. The SDK's gRPC adapter checks
whether the implementation satisfies `AgentToolPlugin` and returns gRPC
`Unimplemented` otherwise. `UnimplementedPlugin.InvokeAgentTool` also returns
`Unimplemented`, so both embedding and non-embedding existing implementations
remain source-compatible. Kandev calls the method only for a declaration present
in the active manifest.
## Runtime Catalog
The runtime catalog is a sorted snapshot containing a process-generation ID,
monotonic revision, and the complete set of active plugin tool descriptors.
The generation ID changes on backend startup. A running agentctl accepts a new
generation and, within one generation, ignores snapshots older than its applied
revision. This prevents delayed refresh requests from restoring stale tools.
Launch and resume carry the complete current snapshot. Live plugin lifecycle
changes push a replacement snapshot to every active execution. Agentctl filters
the snapshot by its backend-owned MCP surface before constructing the effective
registry.
Catalog snapshots are runtime state. The declarations and plugin lifecycle
status remain persisted in the installed plugin record; no new database entity
is introduced.
## Permissions
- Installing, upgrading, enabling, disabling, or uninstalling a plugin retains
the existing operator/admin permission boundary.
- Any task agent whose backend-owned surface matches an active declaration may
discover the tool. There is no per-workspace or per-agent-profile enablement
setting in this version.
- Agentctl injects its bound task and session IDs into the backend request. An
agent cannot select another task or session through arguments.
- The backend applies the same in-session MCP identity scoping used by built-in
task tools and verifies that the bound session belongs to the bound task.
- Plugins remain privileged installed code. The tool declaration does not
sandbox the plugin process or reduce capabilities already granted by its
manifest.
## Failure Modes
- Invalid tool declarations reject install or upgrade without replacing the
previous installed plugin.
- A catalog refresh failure is logged per execution and does not roll back the
plugin lifecycle change. Calls are still rejected against current backend
state even if a client temporarily retains stale discovery. Launch, resume,
or the next catalog change repairs the runtime catalog.
- A call discovered immediately before disable, uninstall, or degradation
fails closed when backend revalidation observes the new state.
- Disabling, uninstalling, or replacing the plugin process cancels calls still
executing in that process. Registry replacement alone does not cancel calls
to unrelated plugins.
- An unavailable or crashed plugin returns an MCP tool error. Kandev does not
retry because the operation may have produced side effects before failure.
- Invalid arguments are rejected before plugin invocation. Backend validation
repeats agentctl's shared MCP schema validation as defense in depth.
- A plugin result that is missing fallback text, exceeds the result limit, or
violates its declared output schema becomes an MCP tool error.
- Plugin panics or transport failures follow the existing supervised plugin
error/recovery lifecycle; recovery republishes the tool catalog.
## Persistence Guarantees
- Tool declarations survive restart as part of the installed manifest record.
- Plugin active/disabled/error status follows the existing plugin lifecycle
persistence contract.
- Runtime catalog generation, revision, and per-agentctl applied snapshots do
not survive restart. They are rebuilt from installed plugin records.
- In-flight calls are not resumed after backend, agentctl, agent, or plugin
process restart.
## Scenarios
- **GIVEN** an active plugin declaring a valid Kanban tool, **WHEN** a Kanban
task agent initializes MCP, **THEN** `tools/list` includes the derived tool
name, description, schemas, and conservative or declared annotations.
- **GIVEN** the same plugin tool targets only `kanban-task`, **WHEN** an Office,
Configuration, or External MCP client lists tools, **THEN** that tool is not
present.
- **GIVEN** a running compatible task agent, **WHEN** an operator enables a
plugin with a matching tool, **THEN** the MCP connection receives one
`tools/list_changed` notification and the next `tools/list` includes it
without restarting the agent process.
- **GIVEN** a running task agent has discovered a plugin tool, **WHEN** the
plugin is disabled before invocation, **THEN** the call fails without
entering the plugin and the refreshed list excludes the tool.
- **GIVEN** a valid call, **WHEN** the plugin executes it, **THEN** the plugin
receives the exact arguments plus Kandev-bound invocation, task, session,
workspace, and surface context and the agent receives fallback text and any
valid structured content.
- **GIVEN** an agent supplies another task or session ID as ordinary tool
arguments, **WHEN** it invokes the tool, **THEN** those values do not replace
the context bound by Kandev.
- **GIVEN** invalid arguments, **WHEN** the agent invokes a plugin tool, **THEN**
Kandev returns a schema-validation error and the plugin receives no RPC.
- **GIVEN** a plugin call times out or its process crashes, **WHEN** the request
completes, **THEN** the agent receives one MCP error and Kandev does not retry
the call.
- **GIVEN** two catalog snapshots arrive out of order, **WHEN** agentctl has
already applied the newer revision from the same generation, **THEN** it
ignores the older snapshot and does not restore removed tools.
- **GIVEN** a client ignores `tools/list_changed`, **WHEN** the plugin catalog
changes, **THEN** Kandev keeps the agent running and backend invocation still
enforces the current catalog; reconnecting exposes the current list.
## Out of Scope
- A plugin-hosted MCP server or transparent federation of arbitrary MCP
resources, prompts, roots, sampling, or elicitation.
- Plugin tools on Configuration or External MCP surfaces.
- Per-workspace, per-workflow, per-agent-profile, or per-tool enablement UI.
- User approval workflows beyond annotations interpreted by the connected MCP
client.
- Streaming tool results, progress notifications, binary/image/audio content,
embedded resources, or resumable calls.
- Automatic retries or exactly-once execution guarantees.
- Sandboxing installed plugin binaries or native UI bundles.