Co-authored-by: Kandev Agent <agent@kandev.dev>
12 KiB
status, created, owner
| status | created | owner |
|---|---|---|
| draft | 2026-08-11 | 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.
Implementation plan: Plugin-Contributed Agent Tools.
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 toconfigurationorexternalMCP 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_changednotification; the agent process, task session, and MCP server do not restart. - A client that does not honor
tools/list_changedsees 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_errorvalue. 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, andopenWorldHint=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, andauthgates 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
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:
type AgentToolPlugin interface {
InvokeAgentTool(context.Context, *AgentToolRequest) (*AgentToolResult, error)
}
The author-facing types are:
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/listincludes 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_changednotification and the nexttools/listincludes 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.