fix(agentctl): register MCP tools with _kandev suffix to match sysprompt (#572)

This commit is contained in:
Carlos Florêncio
2026-04-10 17:17:42 +01:00
committed by GitHub
parent 2872d072cb
commit 2ae996a2c1
13 changed files with 489 additions and 267 deletions
+2 -2
View File
@@ -408,7 +408,7 @@ func scenarioClarification(e *emitter) {
fixedDelay(100)
e.text("Let me ask you a question about the project setup.")
result, err := callMCPTool("kandev", "ask_user_question", clarificationQuestionArgs())
result, err := callMCPTool("kandev", "ask_user_question_kandev", clarificationQuestionArgs())
if err != nil {
e.text(fmt.Sprintf("Question failed: %s", err))
return
@@ -426,7 +426,7 @@ func scenarioClarificationTimeout(e *emitter) {
ctx, cancel := contextWithTimeout(5)
defer cancel()
result, err := callMCPToolCtx(ctx, "kandev", "ask_user_question", clarificationQuestionArgs())
result, err := callMCPToolCtx(ctx, "kandev", "ask_user_question_kandev", clarificationQuestionArgs())
if err != nil {
fixedDelay(50)
if ctx.Err() != nil {
+2 -2
View File
@@ -18,7 +18,7 @@ func TestIsScriptMode(t *testing.T) {
{"e2e:message(\"hello\")", true},
{"e2e:thinking(\"thought\")", true},
{"e2e:delay(100)", true},
{"e2e:mcp:kandev:create_task_plan({})", true},
{"e2e:mcp:kandev:create_task_plan_kandev({})", true},
{"/e2e:simple-message", false},
{"hello world", false},
{"", false},
@@ -155,7 +155,7 @@ func TestSubstituteContextPlaceholders(t *testing.T) {
Kandev Task ID: task-abc-123
Kandev Session ID: sess-xyz-789
</kandev-system>
e2e:mcp:kandev:create_task_plan({"task_id":"{task_id}"})`
e2e:mcp:kandev:create_task_plan_kandev({"task_id":"{task_id}"})`
t.Run("replaces task_id", func(t *testing.T) {
args := map[string]any{"task_id": "{task_id}"}
@@ -13,57 +13,57 @@ import (
func (s *Server) registerConfigWorkflowTools() {
s.mcpServer.AddTool(
mcp.NewToolWithRawSchema("list_workspaces",
mcp.NewToolWithRawSchema("list_workspaces_kandev",
"List all workspaces. Use this first to get workspace IDs.",
json.RawMessage(`{"type":"object","properties":{}}`),
),
s.wrapHandler("list_workspaces", s.listWorkspacesHandler()),
s.wrapHandler("list_workspaces_kandev", s.listWorkspacesHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("list_workflows",
mcp.NewTool("list_workflows_kandev",
mcp.WithDescription("List all workflows in a workspace."),
mcp.WithString("workspace_id", mcp.Required(), mcp.Description("The workspace ID")),
),
s.wrapHandler("list_workflows", s.listWorkflowsHandler()),
s.wrapHandler("list_workflows_kandev", s.listWorkflowsHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("create_workflow",
mcp.NewTool("create_workflow_kandev",
mcp.WithDescription("Create a new workflow in a workspace."),
mcp.WithString("workspace_id", mcp.Required(), mcp.Description("The workspace ID")),
mcp.WithString("name", mcp.Required(), mcp.Description("Workflow name")),
mcp.WithString("description", mcp.Description("Workflow description")),
),
s.wrapHandler("create_workflow", s.createWorkflowHandler()),
s.wrapHandler("create_workflow_kandev", s.createWorkflowHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("update_workflow",
mcp.NewTool("update_workflow_kandev",
mcp.WithDescription("Update an existing workflow."),
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
mcp.WithString("name", mcp.Description("New workflow name")),
mcp.WithString("description", mcp.Description("New workflow description")),
),
s.wrapHandler("update_workflow", s.updateWorkflowHandler()),
s.wrapHandler("update_workflow_kandev", s.updateWorkflowHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("delete_workflow",
mcp.NewTool("delete_workflow_kandev",
mcp.WithDescription("Delete a workflow and all its steps. This is destructive and cannot be undone."),
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID to delete")),
),
s.wrapHandler("delete_workflow", s.deleteWorkflowHandler()),
s.wrapHandler("delete_workflow_kandev", s.deleteWorkflowHandler()),
)
s.registerConfigWorkflowStepTools()
}
func (s *Server) registerConfigWorkflowStepTools() {
s.mcpServer.AddTool(
mcp.NewTool("list_workflow_steps",
mcp.NewTool("list_workflow_steps_kandev",
mcp.WithDescription("List all workflow steps (columns) in a workflow."),
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
),
s.wrapHandler("list_workflow_steps", s.listWorkflowStepsHandler()),
s.wrapHandler("list_workflow_steps_kandev", s.listWorkflowStepsHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("create_workflow_step",
mcp.NewTool("create_workflow_step_kandev",
mcp.WithDescription("Create a new workflow step (column) in a workflow."),
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
mcp.WithString("name", mcp.Required(), mcp.Description("Step name")),
@@ -75,10 +75,10 @@ func (s *Server) registerConfigWorkflowStepTools() {
mcp.WithBoolean("show_in_command_panel", mcp.Description("Show this step in the command panel")),
mcp.WithObject("events", mcp.Description("Event-driven actions. Keys: on_enter, on_exit, on_turn_start, on_turn_complete. Each is an array of {type, config} objects.")),
),
s.wrapHandler("create_workflow_step", s.createWorkflowStepHandler()),
s.wrapHandler("create_workflow_step_kandev", s.createWorkflowStepHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("update_workflow_step",
mcp.NewTool("update_workflow_step_kandev",
mcp.WithDescription("Update an existing workflow step."),
mcp.WithString("step_id", mcp.Required(), mcp.Description("The workflow step ID")),
mcp.WithString("name", mcp.Description("New step name")),
@@ -90,22 +90,22 @@ func (s *Server) registerConfigWorkflowStepTools() {
mcp.WithNumber("auto_archive_after_hours", mcp.Description("Auto-archive tasks after N hours in this step (0 to disable)")),
mcp.WithObject("events", mcp.Description("Event-driven actions. Keys: on_enter, on_exit, on_turn_start, on_turn_complete.")),
),
s.wrapHandler("update_workflow_step", s.updateWorkflowStepHandler()),
s.wrapHandler("update_workflow_step_kandev", s.updateWorkflowStepHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("delete_workflow_step",
mcp.NewTool("delete_workflow_step_kandev",
mcp.WithDescription("Delete a workflow step. This is destructive and cannot be undone."),
mcp.WithString("step_id", mcp.Required(), mcp.Description("The workflow step ID to delete")),
),
s.wrapHandler("delete_workflow_step", s.deleteWorkflowStepHandler()),
s.wrapHandler("delete_workflow_step_kandev", s.deleteWorkflowStepHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("reorder_workflow_steps",
mcp.NewTool("reorder_workflow_steps_kandev",
mcp.WithDescription("Reorder workflow steps by providing the full ordered list of step IDs."),
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
mcp.WithArray("step_ids", mcp.Required(), mcp.Description("Ordered list of step IDs defining the new order")),
),
s.wrapHandler("reorder_workflow_steps", s.reorderWorkflowStepsHandler()),
s.wrapHandler("reorder_workflow_steps_kandev", s.reorderWorkflowStepsHandler()),
)
}
@@ -113,37 +113,37 @@ func (s *Server) registerConfigWorkflowStepTools() {
func (s *Server) registerConfigAgentTools() {
s.mcpServer.AddTool(
mcp.NewToolWithRawSchema("list_agents",
mcp.NewToolWithRawSchema("list_agents_kandev",
"List all configured agents with their profiles.",
json.RawMessage(`{"type":"object","properties":{}}`),
),
s.wrapHandler("list_agents", s.listAgentsHandler()),
s.wrapHandler("list_agents_kandev", s.listAgentsHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("update_agent",
mcp.NewTool("update_agent_kandev",
mcp.WithDescription("Update an existing agent."),
mcp.WithString("agent_id", mcp.Required(), mcp.Description("The agent ID")),
mcp.WithBoolean("supports_mcp", mcp.Description("Whether the agent supports MCP")),
mcp.WithString("mcp_config_path", mcp.Description("Path to MCP config file")),
),
s.wrapHandler("update_agent", s.updateAgentHandler()),
s.wrapHandler("update_agent_kandev", s.updateAgentHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("create_agent_profile",
mcp.NewTool("create_agent_profile_kandev",
mcp.WithDescription("Create a new agent profile for an agent."),
mcp.WithString("agent_id", mcp.Required(), mcp.Description("The agent ID to create a profile for")),
mcp.WithString("name", mcp.Required(), mcp.Description("Profile name")),
mcp.WithString("model", mcp.Required(), mcp.Description("Model name (e.g. 'claude-sonnet-4-5-20250514')")),
mcp.WithBoolean("auto_approve", mcp.Description("Auto-approve permissions (default: false)")),
),
s.wrapHandler("create_agent_profile", s.createAgentProfileHandler()),
s.wrapHandler("create_agent_profile_kandev", s.createAgentProfileHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("delete_agent_profile",
mcp.NewTool("delete_agent_profile_kandev",
mcp.WithDescription("Delete an agent profile. Fails if the profile is used by an active session."),
mcp.WithString("profile_id", mcp.Required(), mcp.Description("The profile ID to delete")),
),
s.wrapHandler("delete_agent_profile", s.deleteAgentProfileHandler()),
s.wrapHandler("delete_agent_profile_kandev", s.deleteAgentProfileHandler()),
)
}
@@ -151,37 +151,37 @@ func (s *Server) registerConfigAgentTools() {
func (s *Server) registerConfigMcpTools() {
s.mcpServer.AddTool(
mcp.NewTool("list_agent_profiles",
mcp.NewTool("list_agent_profiles_kandev",
mcp.WithDescription("List all profiles for an agent."),
mcp.WithString("agent_id", mcp.Required(), mcp.Description("The agent ID")),
),
s.wrapHandler("list_agent_profiles", s.listAgentProfilesHandler()),
s.wrapHandler("list_agent_profiles_kandev", s.listAgentProfilesHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("update_agent_profile",
mcp.NewTool("update_agent_profile_kandev",
mcp.WithDescription("Update an agent profile's settings."),
mcp.WithString("profile_id", mcp.Required(), mcp.Description("The profile ID")),
mcp.WithString("name", mcp.Description("New profile name")),
mcp.WithString("model", mcp.Description("New model name")),
mcp.WithBoolean("auto_approve", mcp.Description("Auto-approve permissions")),
),
s.wrapHandler("update_agent_profile", s.updateAgentProfileHandler()),
s.wrapHandler("update_agent_profile_kandev", s.updateAgentProfileHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("get_mcp_config",
mcp.NewTool("get_mcp_config_kandev",
mcp.WithDescription("Get MCP server configuration for an agent profile."),
mcp.WithString("profile_id", mcp.Required(), mcp.Description("The agent profile ID")),
),
s.wrapHandler("get_mcp_config", s.getMcpConfigHandler()),
s.wrapHandler("get_mcp_config_kandev", s.getMcpConfigHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("update_mcp_config",
mcp.NewTool("update_mcp_config_kandev",
mcp.WithDescription("Update MCP server configuration for an agent profile. Pass the full servers map."),
mcp.WithString("profile_id", mcp.Required(), mcp.Description("The agent profile ID")),
mcp.WithBoolean("enabled", mcp.Description("Whether MCP is enabled for this profile")),
mcp.WithObject("servers", mcp.Description("Full MCP servers map to set. Each key is a server name, value is the server configuration object.")),
),
s.wrapHandler("update_mcp_config", s.updateMcpConfigHandler()),
s.wrapHandler("update_mcp_config_kandev", s.updateMcpConfigHandler()),
)
}
@@ -189,21 +189,21 @@ func (s *Server) registerConfigMcpTools() {
func (s *Server) registerConfigExecutorTools() {
s.mcpServer.AddTool(
mcp.NewToolWithRawSchema("list_executors",
mcp.NewToolWithRawSchema("list_executors_kandev",
"List all executors.",
json.RawMessage(`{"type":"object","properties":{}}`),
),
s.wrapHandler("list_executors", s.listExecutorsHandler()),
s.wrapHandler("list_executors_kandev", s.listExecutorsHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("list_executor_profiles",
mcp.NewTool("list_executor_profiles_kandev",
mcp.WithDescription("List all profiles for an executor."),
mcp.WithString("executor_id", mcp.Required(), mcp.Description("The executor ID")),
),
s.wrapHandler("list_executor_profiles", s.listExecutorProfilesHandler()),
s.wrapHandler("list_executor_profiles_kandev", s.listExecutorProfilesHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("create_executor_profile",
mcp.NewTool("create_executor_profile_kandev",
mcp.WithDescription("Create a new executor profile."),
mcp.WithString("executor_id", mcp.Required(), mcp.Description("The executor ID")),
mcp.WithString("name", mcp.Required(), mcp.Description("Profile name")),
@@ -212,10 +212,10 @@ func (s *Server) registerConfigExecutorTools() {
mcp.WithString("prepare_script", mcp.Description("Script to run before agent starts")),
mcp.WithString("cleanup_script", mcp.Description("Script to run after agent stops")),
),
s.wrapHandler("create_executor_profile", s.createExecutorProfileHandler()),
s.wrapHandler("create_executor_profile_kandev", s.createExecutorProfileHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("update_executor_profile",
mcp.NewTool("update_executor_profile_kandev",
mcp.WithDescription("Update an existing executor profile."),
mcp.WithString("profile_id", mcp.Required(), mcp.Description("The executor profile ID")),
mcp.WithString("name", mcp.Description("New profile name")),
@@ -224,14 +224,14 @@ func (s *Server) registerConfigExecutorTools() {
mcp.WithString("prepare_script", mcp.Description("New prepare script")),
mcp.WithString("cleanup_script", mcp.Description("New cleanup script")),
),
s.wrapHandler("update_executor_profile", s.updateExecutorProfileHandler()),
s.wrapHandler("update_executor_profile_kandev", s.updateExecutorProfileHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("delete_executor_profile",
mcp.NewTool("delete_executor_profile_kandev",
mcp.WithDescription("Delete an executor profile."),
mcp.WithString("profile_id", mcp.Required(), mcp.Description("The executor profile ID to delete")),
),
s.wrapHandler("delete_executor_profile", s.deleteExecutorProfileHandler()),
s.wrapHandler("delete_executor_profile_kandev", s.deleteExecutorProfileHandler()),
)
}
@@ -239,43 +239,43 @@ func (s *Server) registerConfigExecutorTools() {
func (s *Server) registerConfigTaskTools() {
s.mcpServer.AddTool(
mcp.NewTool("list_tasks",
mcp.NewTool("list_tasks_kandev",
mcp.WithDescription("List all tasks in a workflow."),
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
),
s.wrapHandler("list_tasks", s.listTasksHandler()),
s.wrapHandler("list_tasks_kandev", s.listTasksHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("move_task",
mcp.NewTool("move_task_kandev",
mcp.WithDescription("Move a task to a different workflow step."),
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID")),
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("Target workflow ID")),
mcp.WithString("workflow_step_id", mcp.Required(), mcp.Description("Target workflow step ID")),
mcp.WithNumber("position", mcp.Description("Position within the step (0-based)")),
),
s.wrapHandler("move_task", s.moveTaskHandler()),
s.wrapHandler("move_task_kandev", s.moveTaskHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("delete_task",
mcp.NewTool("delete_task_kandev",
mcp.WithDescription("Delete a task permanently."),
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID to delete")),
),
s.wrapHandler("delete_task", s.deleteTaskHandler()),
s.wrapHandler("delete_task_kandev", s.deleteTaskHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("archive_task",
mcp.NewTool("archive_task_kandev",
mcp.WithDescription("Archive a task."),
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID to archive")),
),
s.wrapHandler("archive_task", s.archiveTaskHandler()),
s.wrapHandler("archive_task_kandev", s.archiveTaskHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("update_task_state",
mcp.NewTool("update_task_state_kandev",
mcp.WithDescription("Update the state of a task."),
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID")),
mcp.WithString("state", mcp.Required(), mcp.Description("New state: open, in_progress, complete, blocked, cancelled")),
),
s.wrapHandler("update_task_state", s.updateTaskStateHandler()),
s.wrapHandler("update_task_state_kandev", s.updateTaskStateHandler()),
)
}
@@ -99,7 +99,7 @@ func TestCreateWorkflowHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "create_workflow", map[string]interface{}{
result := callTool(t, s, "create_workflow_kandev", map[string]interface{}{
"workspace_id": "ws-123",
"name": "Sprint Board",
"description": "A sprint workflow",
@@ -118,7 +118,7 @@ func TestCreateWorkflowHandler_MissingWorkspaceID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "create_workflow", map[string]interface{}{
result := callTool(t, s, "create_workflow_kandev", map[string]interface{}{
"name": "Sprint Board",
})
@@ -129,7 +129,7 @@ func TestCreateWorkflowHandler_MissingName(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "create_workflow", map[string]interface{}{
result := callTool(t, s, "create_workflow_kandev", map[string]interface{}{
"workspace_id": "ws-123",
})
@@ -142,7 +142,7 @@ func TestUpdateWorkflowHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "update_workflow", map[string]interface{}{
result := callTool(t, s, "update_workflow_kandev", map[string]interface{}{
"workflow_id": "wf-1",
"name": "Updated",
"description": "New description",
@@ -156,7 +156,7 @@ func TestUpdateWorkflowHandler_MissingWorkflowID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "update_workflow", map[string]interface{}{
result := callTool(t, s, "update_workflow_kandev", map[string]interface{}{
"name": "Updated",
})
@@ -169,7 +169,7 @@ func TestDeleteWorkflowHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "delete_workflow", map[string]interface{}{
result := callTool(t, s, "delete_workflow_kandev", map[string]interface{}{
"workflow_id": "wf-1",
})
@@ -181,7 +181,7 @@ func TestDeleteWorkflowHandler_MissingWorkflowID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "delete_workflow", map[string]interface{}{})
result := callTool(t, s, "delete_workflow_kandev", map[string]interface{}{})
assert.True(t, result.IsError)
}
@@ -192,7 +192,7 @@ func TestCreateWorkflowStepHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "create_workflow_step", map[string]interface{}{
result := callTool(t, s, "create_workflow_step_kandev", map[string]interface{}{
"workflow_id": "wf-123",
"name": "Review",
"color": "#3b82f6",
@@ -209,7 +209,7 @@ func TestCreateWorkflowStepHandler_AllFields(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "create_workflow_step", map[string]interface{}{
result := callTool(t, s, "create_workflow_step_kandev", map[string]interface{}{
"workflow_id": "wf-123",
"name": "Deploy",
"position": float64(0),
@@ -237,7 +237,7 @@ func TestCreateWorkflowStepHandler_MissingWorkflowID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "create_workflow_step", map[string]interface{}{
result := callTool(t, s, "create_workflow_step_kandev", map[string]interface{}{
"name": "Review",
})
@@ -248,7 +248,7 @@ func TestCreateWorkflowStepHandler_MissingName(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "create_workflow_step", map[string]interface{}{
result := callTool(t, s, "create_workflow_step_kandev", map[string]interface{}{
"workflow_id": "wf-123",
})
@@ -261,7 +261,7 @@ func TestUpdateWorkflowStepHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "update_workflow_step", map[string]interface{}{
result := callTool(t, s, "update_workflow_step_kandev", map[string]interface{}{
"step_id": "step-1",
"name": "Updated Name",
})
@@ -276,7 +276,7 @@ func TestUpdateWorkflowStepHandler_AllFields(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "update_workflow_step", map[string]interface{}{
result := callTool(t, s, "update_workflow_step_kandev", map[string]interface{}{
"step_id": "step-1",
"name": "In Review",
"color": "#3b82f6",
@@ -302,7 +302,7 @@ func TestUpdateWorkflowStepHandler_MissingStepID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "update_workflow_step", map[string]interface{}{
result := callTool(t, s, "update_workflow_step_kandev", map[string]interface{}{
"name": "Updated",
})
@@ -317,7 +317,7 @@ func TestListAgentsHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "list_agents", map[string]interface{}{})
result := callTool(t, s, "list_agents_kandev", map[string]interface{}{})
assert.False(t, result.IsError)
assert.Equal(t, ws.ActionMCPListAgents, backend.lastAction)
@@ -329,7 +329,7 @@ func TestCreateAgentProfileHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "create_agent_profile", map[string]interface{}{
result := callTool(t, s, "create_agent_profile_kandev", map[string]interface{}{
"agent_id": "agent-1",
"name": "My Profile",
"model": "claude-sonnet-4-5-20250514",
@@ -343,7 +343,7 @@ func TestCreateAgentProfileHandler_MissingAgentID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "create_agent_profile", map[string]interface{}{
result := callTool(t, s, "create_agent_profile_kandev", map[string]interface{}{
"name": "My Profile",
"model": "claude-sonnet-4-5-20250514",
})
@@ -355,7 +355,7 @@ func TestCreateAgentProfileHandler_MissingModel(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "create_agent_profile", map[string]interface{}{
result := callTool(t, s, "create_agent_profile_kandev", map[string]interface{}{
"agent_id": "agent-1",
"name": "My Profile",
})
@@ -369,7 +369,7 @@ func TestUpdateAgentHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "update_agent", map[string]interface{}{
result := callTool(t, s, "update_agent_kandev", map[string]interface{}{
"agent_id": "agent-1",
"supports_mcp": true,
})
@@ -382,7 +382,7 @@ func TestUpdateAgentHandler_MissingAgentID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "update_agent", map[string]interface{}{
result := callTool(t, s, "update_agent_kandev", map[string]interface{}{
"supports_mcp": true,
})
@@ -395,7 +395,7 @@ func TestDeleteAgentProfileHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "delete_agent_profile", map[string]interface{}{
result := callTool(t, s, "delete_agent_profile_kandev", map[string]interface{}{
"profile_id": "profile-1",
})
@@ -407,7 +407,7 @@ func TestDeleteAgentProfileHandler_MissingProfileID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "delete_agent_profile", map[string]interface{}{})
result := callTool(t, s, "delete_agent_profile_kandev", map[string]interface{}{})
assert.True(t, result.IsError)
}
@@ -420,7 +420,7 @@ func TestListAgentProfilesHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "list_agent_profiles", map[string]interface{}{
result := callTool(t, s, "list_agent_profiles_kandev", map[string]interface{}{
"agent_id": "agent-1",
})
@@ -432,7 +432,7 @@ func TestListAgentProfilesHandler_MissingAgentID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "list_agent_profiles", map[string]interface{}{})
result := callTool(t, s, "list_agent_profiles_kandev", map[string]interface{}{})
assert.True(t, result.IsError)
}
@@ -443,7 +443,7 @@ func TestUpdateAgentProfileHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "update_agent_profile", map[string]interface{}{
result := callTool(t, s, "update_agent_profile_kandev", map[string]interface{}{
"profile_id": "profile-1",
"name": "Updated Profile",
"model": "claude-3.5-sonnet",
@@ -457,7 +457,7 @@ func TestUpdateAgentProfileHandler_MissingProfileID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "update_agent_profile", map[string]interface{}{
result := callTool(t, s, "update_agent_profile_kandev", map[string]interface{}{
"name": "Updated",
})
@@ -470,7 +470,7 @@ func TestGetMcpConfigHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "get_mcp_config", map[string]interface{}{
result := callTool(t, s, "get_mcp_config_kandev", map[string]interface{}{
"profile_id": "p-1",
})
@@ -482,7 +482,7 @@ func TestGetMcpConfigHandler_MissingProfileID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "get_mcp_config", map[string]interface{}{})
result := callTool(t, s, "get_mcp_config_kandev", map[string]interface{}{})
assert.True(t, result.IsError)
}
@@ -493,7 +493,7 @@ func TestUpdateMcpConfigHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "update_mcp_config", map[string]interface{}{
result := callTool(t, s, "update_mcp_config_kandev", map[string]interface{}{
"profile_id": "p-1",
"enabled": true,
})
@@ -506,7 +506,7 @@ func TestUpdateMcpConfigHandler_MissingProfileID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "update_mcp_config", map[string]interface{}{})
result := callTool(t, s, "update_mcp_config_kandev", map[string]interface{}{})
assert.True(t, result.IsError)
}
@@ -519,7 +519,7 @@ func TestListExecutorsHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "list_executors", map[string]interface{}{})
result := callTool(t, s, "list_executors_kandev", map[string]interface{}{})
assert.False(t, result.IsError)
assert.Equal(t, ws.ActionMCPListExecutors, backend.lastAction)
@@ -531,7 +531,7 @@ func TestListExecutorProfilesHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "list_executor_profiles", map[string]interface{}{
result := callTool(t, s, "list_executor_profiles_kandev", map[string]interface{}{
"executor_id": "exec-1",
})
@@ -543,7 +543,7 @@ func TestListExecutorProfilesHandler_MissingExecutorID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "list_executor_profiles", map[string]interface{}{})
result := callTool(t, s, "list_executor_profiles_kandev", map[string]interface{}{})
assert.True(t, result.IsError)
}
@@ -554,7 +554,7 @@ func TestCreateExecutorProfileHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "create_executor_profile", map[string]interface{}{
result := callTool(t, s, "create_executor_profile_kandev", map[string]interface{}{
"executor_id": "exec-1",
"name": "Default",
})
@@ -571,7 +571,7 @@ func TestCreateExecutorProfileHandler_MissingExecutorID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "create_executor_profile", map[string]interface{}{
result := callTool(t, s, "create_executor_profile_kandev", map[string]interface{}{
"name": "Default",
})
@@ -582,7 +582,7 @@ func TestCreateExecutorProfileHandler_MissingName(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "create_executor_profile", map[string]interface{}{
result := callTool(t, s, "create_executor_profile_kandev", map[string]interface{}{
"executor_id": "exec-1",
})
@@ -595,7 +595,7 @@ func TestUpdateExecutorProfileHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "update_executor_profile", map[string]interface{}{
result := callTool(t, s, "update_executor_profile_kandev", map[string]interface{}{
"profile_id": "prof-1",
"name": "Updated Profile",
})
@@ -608,7 +608,7 @@ func TestUpdateExecutorProfileHandler_MissingProfileID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "update_executor_profile", map[string]interface{}{
result := callTool(t, s, "update_executor_profile_kandev", map[string]interface{}{
"name": "Updated",
})
@@ -621,7 +621,7 @@ func TestDeleteExecutorProfileHandler_Success(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "delete_executor_profile", map[string]interface{}{
result := callTool(t, s, "delete_executor_profile_kandev", map[string]interface{}{
"profile_id": "prof-1",
})
@@ -633,7 +633,7 @@ func TestDeleteExecutorProfileHandler_MissingProfileID(t *testing.T) {
backend := &testBackend{}
s := newTestServer(t, backend)
result := callTool(t, s, "delete_executor_profile", map[string]interface{}{})
result := callTool(t, s, "delete_executor_profile_kandev", map[string]interface{}{})
assert.True(t, result.IsError)
}
@@ -646,7 +646,7 @@ func TestForwardToBackend_BackendError(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "list_agents", map[string]interface{}{})
result := callTool(t, s, "list_agents_kandev", map[string]interface{}{})
assert.True(t, result.IsError)
}
@@ -662,7 +662,7 @@ func TestForwardToBackend_ResultContainsJSON(t *testing.T) {
}
s := newTestServer(t, backend)
result := callTool(t, s, "list_agents", map[string]interface{}{})
result := callTool(t, s, "list_agents_kandev", map[string]interface{}{})
assert.False(t, result.IsError)
require.NotEmpty(t, result.Content)
@@ -20,7 +20,7 @@ func TestCreateTask_ToolSchema_HasParentID(t *testing.T) {
s := newTaskModeServer(t, backend, "task-current")
toolsMap := s.mcpServer.ListTools()
tool, ok := toolsMap["create_task"]
tool, ok := toolsMap["create_task_kandev"]
require.True(t, ok, "create_task tool not registered")
schema, err := json.Marshal(tool.Tool.InputSchema)
@@ -54,7 +54,7 @@ func TestCreateTask_SelfResolvesToTaskID(t *testing.T) {
}
s := newTaskModeServer(t, backend, "task-current")
result := callTool(t, s, "create_task", map[string]interface{}{
result := callTool(t, s, "create_task_kandev", map[string]interface{}{
"title": "Write tests",
"parent_id": "self",
})
@@ -72,7 +72,7 @@ func TestCreateTask_SelfWithNoTaskContext_ReturnsError(t *testing.T) {
backend := &testBackend{}
s := newTaskModeServer(t, backend, "")
result := callTool(t, s, "create_task", map[string]interface{}{
result := callTool(t, s, "create_task_kandev", map[string]interface{}{
"title": "Write tests",
"parent_id": "self",
})
@@ -86,7 +86,7 @@ func TestCreateTask_ExplicitParentID(t *testing.T) {
}
s := newTaskModeServer(t, backend, "task-current")
result := callTool(t, s, "create_task", map[string]interface{}{
result := callTool(t, s, "create_task_kandev", map[string]interface{}{
"title": "Fix bug",
"parent_id": "task-abc",
})
@@ -103,7 +103,7 @@ func TestCreateTask_NoParentID_RequiresWorkspaceAndWorkflow(t *testing.T) {
s := newTaskModeServer(t, backend, "task-current")
// No parent_id and no workspace/workflow -> error
result := callTool(t, s, "create_task", map[string]interface{}{
result := callTool(t, s, "create_task_kandev", map[string]interface{}{
"title": "Standalone task",
})
@@ -116,7 +116,7 @@ func TestCreateTask_NoParentID_WithIDs_CreatesTopLevelTask(t *testing.T) {
}
s := newTaskModeServer(t, backend, "task-current")
result := callTool(t, s, "create_task", map[string]interface{}{
result := callTool(t, s, "create_task_kandev", map[string]interface{}{
"title": "Standalone",
"workspace_id": "ws-1",
"workflow_id": "wf-1",
@@ -262,36 +262,36 @@ func (s *Server) registerKanbanTools() {
// omitempty which drops empty properties maps, causing OpenAI API validation
// errors ("object schema missing properties").
s.mcpServer.AddTool(
mcp.NewToolWithRawSchema("list_workspaces",
mcp.NewToolWithRawSchema("list_workspaces_kandev",
"List all workspaces. Use this first to get workspace IDs.",
json.RawMessage(`{"type":"object","properties":{}}`),
),
s.wrapHandler("list_workspaces", s.listWorkspacesHandler()),
s.wrapHandler("list_workspaces_kandev", s.listWorkspacesHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("list_workflows",
mcp.NewTool("list_workflows_kandev",
mcp.WithDescription("List all workflows in a workspace."),
mcp.WithString("workspace_id", mcp.Required(), mcp.Description("The workspace ID")),
),
s.wrapHandler("list_workflows", s.listWorkflowsHandler()),
s.wrapHandler("list_workflows_kandev", s.listWorkflowsHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("list_workflow_steps",
mcp.NewTool("list_workflow_steps_kandev",
mcp.WithDescription("List all workflow steps in a workflow."),
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
),
s.wrapHandler("list_workflow_steps", s.listWorkflowStepsHandler()),
s.wrapHandler("list_workflow_steps_kandev", s.listWorkflowStepsHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("list_tasks",
mcp.NewTool("list_tasks_kandev",
mcp.WithDescription("List all tasks in a workflow."),
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
),
s.wrapHandler("list_tasks", s.listTasksHandler()),
s.wrapHandler("list_tasks_kandev", s.listTasksHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("create_task",
mcp.WithDescription("Create a new task or subtask and auto-start an agent on it. For subtasks (parent_id='self'), the executor profile and agent profile are automatically inherited from the parent session — no need to ask. For top-level tasks, use ask_user_question first if you do not already know which executor profile and agent profile the user wants to use. IMPORTANT: 'description' is the initial prompt the sub-agent receives — it is the ONLY context the sub-agent has to start working. Always provide a detailed description for subtasks."),
mcp.NewTool("create_task_kandev",
mcp.WithDescription("Create a new task or subtask and auto-start an agent on it. For subtasks (parent_id='self'), the executor profile and agent profile are automatically inherited from the parent session — no need to ask. For top-level tasks, use ask_user_question_kandev first if you do not already know which executor profile and agent profile the user wants to use. IMPORTANT: 'description' is the initial prompt the sub-agent receives — it is the ONLY context the sub-agent has to start working. Always provide a detailed description for subtasks."),
mcp.WithString("parent_id", mcp.Description("Parent task ID for subtasks. Use 'self' to create a subtask of your current task. Omit to create a top-level task.")),
mcp.WithString("workspace_id", mcp.Description("The workspace ID (required for top-level tasks, inherited from parent for subtasks)")),
mcp.WithString("workflow_id", mcp.Description("The workflow ID (required for top-level tasks, inherited from parent for subtasks)")),
@@ -301,37 +301,37 @@ func (s *Server) registerKanbanTools() {
mcp.WithString("agent_profile_id", mcp.Description("Agent profile ID to use. For subtasks, inherited from the parent session. For top-level tasks, ask the user which agent profile they want (e.g. Claude Code, OpenCode) if not already known.")),
mcp.WithString("executor_profile_id", mcp.Description("Executor profile ID to use (determines the runtime environment: local, worktree, docker, etc.). For subtasks, inherited from the parent session. For top-level tasks, ask the user which executor profile they want if not already known.")),
),
s.wrapHandler("create_task", s.createTaskHandler()),
s.wrapHandler("create_task_kandev", s.createTaskHandler()),
)
s.mcpServer.AddTool(
mcp.NewToolWithRawSchema("list_agents",
"List all configured agents with their profiles. Use this to find available agent_profile_ids for create_task.",
mcp.NewToolWithRawSchema("list_agents_kandev",
"List all configured agents with their profiles. Use this to find available agent_profile_ids for create_task_kandev.",
json.RawMessage(`{"type":"object","properties":{}}`),
),
s.wrapHandler("list_agents", s.listAgentsHandler()),
s.wrapHandler("list_agents_kandev", s.listAgentsHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("list_executor_profiles",
mcp.WithDescription("List all profiles for an executor. Use this to find available executor_profile_ids for create_task. Standard executor IDs: exec-local (standalone process), exec-worktree (git worktree), exec-local-docker (Docker container), exec-sprites (cloud)."),
mcp.NewTool("list_executor_profiles_kandev",
mcp.WithDescription("List all profiles for an executor. Use this to find available executor_profile_ids for create_task_kandev. Standard executor IDs: exec-local (standalone process), exec-worktree (git worktree), exec-local-docker (Docker container), exec-sprites (cloud)."),
mcp.WithString("executor_id", mcp.Required(), mcp.Description("The executor ID (e.g. exec-local, exec-worktree, exec-local-docker, exec-sprites)")),
),
s.wrapHandler("list_executor_profiles", s.listExecutorProfilesHandler()),
s.wrapHandler("list_executor_profiles_kandev", s.listExecutorProfilesHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("update_task",
mcp.NewTool("update_task_kandev",
mcp.WithDescription("Update an existing task."),
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID")),
mcp.WithString("title", mcp.Description("New title")),
mcp.WithString("description", mcp.Description("New description")),
mcp.WithString("state", mcp.Description("New state: not_started, in_progress, etc.")),
),
s.wrapHandler("update_task", s.updateTaskHandler()),
s.wrapHandler("update_task_kandev", s.updateTaskHandler()),
)
}
func (s *Server) registerInteractionTools() {
s.mcpServer.AddTool(
mcp.NewTool("ask_user_question",
mcp.NewTool("ask_user_question_kandev",
mcp.WithDescription(`Ask the user a clarifying question with multiple choice options.
Use this tool when you need user input to proceed. The user will see the prompt and can select one of the options or provide a custom text response.
@@ -381,41 +381,41 @@ Another example:
),
mcp.WithString("context", mcp.Description("Optional background information to help the user understand why you're asking this question.")),
),
s.wrapHandler("ask_user_question", s.askUserQuestionHandler()),
s.wrapHandler("ask_user_question_kandev", s.askUserQuestionHandler()),
)
}
func (s *Server) registerPlanTools() {
s.mcpServer.AddTool(
mcp.NewTool("create_task_plan",
mcp.NewTool("create_task_plan_kandev",
mcp.WithDescription("Create or save a task plan. Use this to save your implementation plan for the current task."),
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID to create a plan for")),
mcp.WithString("content", mcp.Required(), mcp.Description("The plan content in markdown format")),
mcp.WithString("title", mcp.Description("Optional title for the plan (default: 'Plan')")),
),
s.wrapHandler("create_task_plan", s.createTaskPlanHandler()),
s.wrapHandler("create_task_plan_kandev", s.createTaskPlanHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("get_task_plan",
mcp.NewTool("get_task_plan_kandev",
mcp.WithDescription("Get the current plan for a task. Use this to retrieve an existing plan, including any user edits."),
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID to get the plan for")),
),
s.wrapHandler("get_task_plan", s.getTaskPlanHandler()),
s.wrapHandler("get_task_plan_kandev", s.getTaskPlanHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("update_task_plan",
mcp.NewTool("update_task_plan_kandev",
mcp.WithDescription("Update an existing task plan. Use this to modify the plan during implementation."),
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID to update the plan for")),
mcp.WithString("content", mcp.Required(), mcp.Description("The updated plan content in markdown format")),
mcp.WithString("title", mcp.Description("Optional new title for the plan")),
),
s.wrapHandler("update_task_plan", s.updateTaskPlanHandler()),
s.wrapHandler("update_task_plan_kandev", s.updateTaskPlanHandler()),
)
s.mcpServer.AddTool(
mcp.NewTool("delete_task_plan",
mcp.NewTool("delete_task_plan_kandev",
mcp.WithDescription("Delete a task plan."),
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID to delete the plan for")),
),
s.wrapHandler("delete_task_plan", s.deleteTaskPlanHandler()),
s.wrapHandler("delete_task_plan_kandev", s.deleteTaskPlanHandler()),
)
}
@@ -36,49 +36,49 @@ func TestServerModeTask_RegistersCorrectTools(t *testing.T) {
tools := getRegisteredToolNames(s)
// Task mode should have kanban tools
assert.Contains(t, tools, "list_workspaces")
assert.Contains(t, tools, "list_workflows")
assert.Contains(t, tools, "list_workflow_steps")
assert.Contains(t, tools, "list_tasks")
assert.Contains(t, tools, "create_task")
assert.Contains(t, tools, "update_task")
assert.Contains(t, tools, "list_workspaces_kandev")
assert.Contains(t, tools, "list_workflows_kandev")
assert.Contains(t, tools, "list_workflow_steps_kandev")
assert.Contains(t, tools, "list_tasks_kandev")
assert.Contains(t, tools, "create_task_kandev")
assert.Contains(t, tools, "update_task_kandev")
// Task mode should have plan tools
assert.Contains(t, tools, "create_task_plan")
assert.Contains(t, tools, "get_task_plan")
assert.Contains(t, tools, "update_task_plan")
assert.Contains(t, tools, "delete_task_plan")
assert.Contains(t, tools, "create_task_plan_kandev")
assert.Contains(t, tools, "get_task_plan_kandev")
assert.Contains(t, tools, "update_task_plan_kandev")
assert.Contains(t, tools, "delete_task_plan_kandev")
// Task mode should have interaction tools
assert.Contains(t, tools, "ask_user_question")
assert.Contains(t, tools, "ask_user_question_kandev")
// Task mode should have profile listing tools (needed for create_task)
assert.Contains(t, tools, "list_agents")
assert.Contains(t, tools, "list_executor_profiles")
assert.Contains(t, tools, "list_agents_kandev")
assert.Contains(t, tools, "list_executor_profiles_kandev")
// Task mode should NOT have config/mutation tools
assert.NotContains(t, tools, "create_workflow")
assert.NotContains(t, tools, "update_workflow")
assert.NotContains(t, tools, "delete_workflow")
assert.NotContains(t, tools, "create_workflow_step")
assert.NotContains(t, tools, "update_workflow_step")
assert.NotContains(t, tools, "update_agent")
assert.NotContains(t, tools, "create_agent_profile")
assert.NotContains(t, tools, "delete_agent_profile")
assert.NotContains(t, tools, "list_agent_profiles")
assert.NotContains(t, tools, "update_agent_profile")
assert.NotContains(t, tools, "get_mcp_config")
assert.NotContains(t, tools, "update_mcp_config")
assert.NotContains(t, tools, "move_task")
assert.NotContains(t, tools, "delete_task")
assert.NotContains(t, tools, "archive_task")
assert.NotContains(t, tools, "list_executors")
assert.NotContains(t, tools, "create_executor_profile")
assert.NotContains(t, tools, "update_executor_profile")
assert.NotContains(t, tools, "delete_executor_profile")
assert.NotContains(t, tools, "update_task_state")
assert.NotContains(t, tools, "delete_workflow_step")
assert.NotContains(t, tools, "reorder_workflow_steps")
assert.NotContains(t, tools, "create_workflow_kandev")
assert.NotContains(t, tools, "update_workflow_kandev")
assert.NotContains(t, tools, "delete_workflow_kandev")
assert.NotContains(t, tools, "create_workflow_step_kandev")
assert.NotContains(t, tools, "update_workflow_step_kandev")
assert.NotContains(t, tools, "update_agent_kandev")
assert.NotContains(t, tools, "create_agent_profile_kandev")
assert.NotContains(t, tools, "delete_agent_profile_kandev")
assert.NotContains(t, tools, "list_agent_profiles_kandev")
assert.NotContains(t, tools, "update_agent_profile_kandev")
assert.NotContains(t, tools, "get_mcp_config_kandev")
assert.NotContains(t, tools, "update_mcp_config_kandev")
assert.NotContains(t, tools, "move_task_kandev")
assert.NotContains(t, tools, "delete_task_kandev")
assert.NotContains(t, tools, "archive_task_kandev")
assert.NotContains(t, tools, "list_executors_kandev")
assert.NotContains(t, tools, "create_executor_profile_kandev")
assert.NotContains(t, tools, "update_executor_profile_kandev")
assert.NotContains(t, tools, "delete_executor_profile_kandev")
assert.NotContains(t, tools, "update_task_state_kandev")
assert.NotContains(t, tools, "delete_workflow_step_kandev")
assert.NotContains(t, tools, "reorder_workflow_steps_kandev")
}
func TestServerModeConfig_RegistersCorrectTools(t *testing.T) {
@@ -92,55 +92,55 @@ func TestServerModeConfig_RegistersCorrectTools(t *testing.T) {
tools := getRegisteredToolNames(s)
// Config mode should have workflow config tools
assert.Contains(t, tools, "list_workspaces")
assert.Contains(t, tools, "list_workflows")
assert.Contains(t, tools, "create_workflow")
assert.Contains(t, tools, "update_workflow")
assert.Contains(t, tools, "delete_workflow")
assert.Contains(t, tools, "list_workflow_steps")
assert.Contains(t, tools, "create_workflow_step")
assert.Contains(t, tools, "update_workflow_step")
assert.Contains(t, tools, "delete_workflow_step")
assert.Contains(t, tools, "reorder_workflow_steps")
assert.Contains(t, tools, "list_workspaces_kandev")
assert.Contains(t, tools, "list_workflows_kandev")
assert.Contains(t, tools, "create_workflow_kandev")
assert.Contains(t, tools, "update_workflow_kandev")
assert.Contains(t, tools, "delete_workflow_kandev")
assert.Contains(t, tools, "list_workflow_steps_kandev")
assert.Contains(t, tools, "create_workflow_step_kandev")
assert.Contains(t, tools, "update_workflow_step_kandev")
assert.Contains(t, tools, "delete_workflow_step_kandev")
assert.Contains(t, tools, "reorder_workflow_steps_kandev")
// Config mode should have agent tools
assert.Contains(t, tools, "list_agents")
assert.Contains(t, tools, "update_agent")
assert.Contains(t, tools, "create_agent_profile")
assert.Contains(t, tools, "delete_agent_profile")
assert.Contains(t, tools, "list_agents_kandev")
assert.Contains(t, tools, "update_agent_kandev")
assert.Contains(t, tools, "create_agent_profile_kandev")
assert.Contains(t, tools, "delete_agent_profile_kandev")
// Config mode should have MCP config tools
assert.Contains(t, tools, "list_agent_profiles")
assert.Contains(t, tools, "update_agent_profile")
assert.Contains(t, tools, "get_mcp_config")
assert.Contains(t, tools, "update_mcp_config")
assert.Contains(t, tools, "list_agent_profiles_kandev")
assert.Contains(t, tools, "update_agent_profile_kandev")
assert.Contains(t, tools, "get_mcp_config_kandev")
assert.Contains(t, tools, "update_mcp_config_kandev")
// Config mode should have executor profile tools
assert.Contains(t, tools, "list_executors")
assert.Contains(t, tools, "list_executor_profiles")
assert.Contains(t, tools, "create_executor_profile")
assert.Contains(t, tools, "update_executor_profile")
assert.Contains(t, tools, "delete_executor_profile")
assert.Contains(t, tools, "list_executors_kandev")
assert.Contains(t, tools, "list_executor_profiles_kandev")
assert.Contains(t, tools, "create_executor_profile_kandev")
assert.Contains(t, tools, "update_executor_profile_kandev")
assert.Contains(t, tools, "delete_executor_profile_kandev")
// Config mode should have task tools
assert.Contains(t, tools, "list_tasks")
assert.Contains(t, tools, "move_task")
assert.Contains(t, tools, "delete_task")
assert.Contains(t, tools, "archive_task")
assert.Contains(t, tools, "update_task_state")
assert.Contains(t, tools, "list_tasks_kandev")
assert.Contains(t, tools, "move_task_kandev")
assert.Contains(t, tools, "delete_task_kandev")
assert.Contains(t, tools, "archive_task_kandev")
assert.Contains(t, tools, "update_task_state_kandev")
// Config mode should have interaction tools
assert.Contains(t, tools, "ask_user_question")
assert.Contains(t, tools, "ask_user_question_kandev")
// Config mode should NOT have plan tools
assert.NotContains(t, tools, "create_task_plan")
assert.NotContains(t, tools, "get_task_plan")
assert.NotContains(t, tools, "update_task_plan")
assert.NotContains(t, tools, "delete_task_plan")
assert.NotContains(t, tools, "create_task_plan_kandev")
assert.NotContains(t, tools, "get_task_plan_kandev")
assert.NotContains(t, tools, "update_task_plan_kandev")
assert.NotContains(t, tools, "delete_task_plan_kandev")
// Config mode should NOT have task-mode kanban create/update tools
assert.NotContains(t, tools, "create_task")
assert.NotContains(t, tools, "update_task")
assert.NotContains(t, tools, "create_task_kandev")
assert.NotContains(t, tools, "update_task_kandev")
}
func TestServerModeDefault_DefaultsToTask(t *testing.T) {
@@ -153,9 +153,9 @@ func TestServerModeDefault_DefaultsToTask(t *testing.T) {
assert.Equal(t, ModeTask, s.mode)
tools := getRegisteredToolNames(s)
assert.Contains(t, tools, "create_task")
assert.Contains(t, tools, "create_task_plan")
assert.NotContains(t, tools, "create_workflow_step")
assert.Contains(t, tools, "create_task_kandev")
assert.Contains(t, tools, "create_task_plan_kandev")
assert.NotContains(t, tools, "create_workflow_step_kandev")
}
func TestServerModeConfig_DisableAskQuestion(t *testing.T) {
@@ -167,9 +167,9 @@ func TestServerModeConfig_DisableAskQuestion(t *testing.T) {
require.NotNil(t, s)
tools := getRegisteredToolNames(s)
assert.NotContains(t, tools, "ask_user_question")
assert.Contains(t, tools, "list_agents")
assert.Contains(t, tools, "create_workflow_step")
assert.NotContains(t, tools, "ask_user_question_kandev")
assert.Contains(t, tools, "list_agents_kandev")
assert.Contains(t, tools, "create_workflow_step_kandev")
}
func TestServerModeTask_DisableAskQuestion(t *testing.T) {
@@ -181,9 +181,9 @@ func TestServerModeTask_DisableAskQuestion(t *testing.T) {
require.NotNil(t, s)
tools := getRegisteredToolNames(s)
assert.NotContains(t, tools, "ask_user_question")
assert.Contains(t, tools, "create_task")
assert.Contains(t, tools, "create_task_plan")
assert.NotContains(t, tools, "ask_user_question_kandev")
assert.Contains(t, tools, "create_task_kandev")
assert.Contains(t, tools, "create_task_plan_kandev")
}
func TestServerModeTask_ToolCount(t *testing.T) {
@@ -217,9 +217,9 @@ func TestServerModeConfig_ToolDescriptions(t *testing.T) {
toolsMap := s.mcpServer.ListTools()
assert.Contains(t, toolsMap["create_workflow_step"].Tool.Description, "Create a new workflow step")
assert.Contains(t, toolsMap["list_agents"].Tool.Description, "List all configured agents")
assert.Contains(t, toolsMap["get_mcp_config"].Tool.Description, "Get MCP server configuration")
assert.Contains(t, toolsMap["create_workflow_step_kandev"].Tool.Description, "Create a new workflow step")
assert.Contains(t, toolsMap["list_agents_kandev"].Tool.Description, "List all configured agents")
assert.Contains(t, toolsMap["get_mcp_config_kandev"].Tool.Description, "Get MCP server configuration")
}
func TestServerModeConstants(t *testing.T) {
@@ -0,0 +1,220 @@
package mcp
import (
"regexp"
"sort"
"strings"
"testing"
"github.com/kandev/kandev/internal/sysprompt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// kandevToolRef matches any `<name>_kandev` identifier appearing in a prompt.
// The regex enforces snake_case and requires the _kandev suffix at a word boundary.
var kandevToolRef = regexp.MustCompile(`\b[a-z][a-z0-9_]*_kandev\b`)
// extractKandevTools returns the unique set of "<name>_kandev" tool names
// referenced anywhere inside the given prompt text.
func extractKandevTools(prompt string) map[string]struct{} {
out := make(map[string]struct{})
for _, m := range kandevToolRef.FindAllString(prompt, -1) {
out[m] = struct{}{}
}
return out
}
// findBareToolReferences scans `prompt` for any whole-word occurrence of a
// `bareName`. Returns the sorted list of bare names found. Used to catch
// sysprompt drift in the opposite direction of the v0.28 bug — i.e. a prompt
// that says `get_task_plan` (no suffix) when the registered tool is
// `get_task_plan_kandev`.
//
// `\b` in Go's RE2 fires at a transition between a word char (`[A-Za-z0-9_]`)
// and a non-word char. Since `_` is a word char, `\b<bare>\b` cannot match
// inside `<bare>_kandev` — the trailing `\b` requires a non-word char after the
// last letter of `bare`, and `_` fails that test. So this regex naturally
// distinguishes bare references from the suffixed form without an explicit
// suffix guard.
func findBareToolReferences(prompt string, bareNames map[string]struct{}) []string {
var found []string
for bare := range bareNames {
re := regexp.MustCompile(`\b` + regexp.QuoteMeta(bare) + `\b`)
if re.MatchString(prompt) {
found = append(found, bare)
}
}
sort.Strings(found)
return found
}
// bareNamesOf strips the `_kandev` suffix from every registered tool name and
// returns the bare set, used by findBareToolReferences as the haystack of
// candidates to scan for.
func bareNamesOf(registered map[string]struct{}) map[string]struct{} {
const suffix = "_kandev"
out := make(map[string]struct{}, len(registered))
for name := range registered {
out[strings.TrimSuffix(name, suffix)] = struct{}{}
}
return out
}
// TestSyspromptToolNames_MatchMCPTaskMode verifies that every `<name>_kandev`
// tool referenced in the task-mode prompts (PlanMode, KandevContext,
// DefaultPlanPrefix) is actually registered by an MCP server in ModeTask.
//
// This is the regression test for the v0.28 bug where the sysprompt told
// agents to call tools like `get_task_plan_kandev` but the MCP server
// registered them as `get_task_plan` (no suffix), causing "Tool not found"
// errors at runtime.
func TestSyspromptToolNames_MatchMCPTaskMode(t *testing.T) {
log := newTestLogger(t)
backend := NewChannelBackendClient(log)
defer backend.Close()
s := New(backend, "test-session", "test-task", 10005, log, "", false, ModeTask)
require.NotNil(t, s)
registered := make(map[string]struct{})
for _, name := range getRegisteredToolNames(s) {
registered[name] = struct{}{}
}
referenced := make(map[string]struct{})
for name := range extractKandevTools(sysprompt.PlanMode) {
referenced[name] = struct{}{}
}
for name := range extractKandevTools(sysprompt.KandevContext) {
referenced[name] = struct{}{}
}
for name := range extractKandevTools(sysprompt.DefaultPlanPrefix) {
referenced[name] = struct{}{}
}
require.NotEmpty(t, referenced, "expected task-mode prompts to reference at least one _kandev tool")
for name := range referenced {
_, ok := registered[name]
assert.True(t, ok,
"tool %q is referenced in task-mode sysprompt but not registered by the MCP server in ModeTask",
name)
}
}
// TestSyspromptToolNames_MatchMCPConfigMode verifies that every `<name>_kandev`
// tool referenced in ConfigContext is registered by an MCP server in ModeConfig.
func TestSyspromptToolNames_MatchMCPConfigMode(t *testing.T) {
log := newTestLogger(t)
backend := NewChannelBackendClient(log)
defer backend.Close()
s := New(backend, "test-session", "test-task", 10005, log, "", false, ModeConfig)
require.NotNil(t, s)
registered := make(map[string]struct{})
for _, name := range getRegisteredToolNames(s) {
registered[name] = struct{}{}
}
referenced := extractKandevTools(sysprompt.ConfigContext)
require.NotEmpty(t, referenced, "expected ConfigContext to reference at least one _kandev tool")
for name := range referenced {
_, ok := registered[name]
assert.True(t, ok,
"tool %q is referenced in ConfigContext but not registered by the MCP server in ModeConfig",
name)
}
}
// TestSyspromptToolNames_NoBareToolReferences catches the opposite drift: a
// prompt that mentions a registered tool by its bare name (without the
// `_kandev` suffix). Without this check, a typo like
// `get_task_plan` in a sysprompt would silently slip past the other tests
// because they only inspect `_kandev`-suffixed mentions.
func TestSyspromptToolNames_NoBareToolReferences(t *testing.T) {
log := newTestLogger(t)
backend := NewChannelBackendClient(log)
defer backend.Close()
taskServer := New(backend, "test-session", "test-task", 10005, log, "", false, ModeTask)
require.NotNil(t, taskServer)
configServer := New(backend, "test-session", "test-task", 10005, log, "", false, ModeConfig)
require.NotNil(t, configServer)
registered := make(map[string]struct{})
for _, name := range getRegisteredToolNames(taskServer) {
registered[name] = struct{}{}
}
for _, name := range getRegisteredToolNames(configServer) {
registered[name] = struct{}{}
}
bareNames := bareNamesOf(registered)
cases := map[string]string{
"PlanMode": sysprompt.PlanMode,
"KandevContext": sysprompt.KandevContext,
"DefaultPlanPrefix": sysprompt.DefaultPlanPrefix,
"ConfigContext": sysprompt.ConfigContext,
}
for name, prompt := range cases {
bare := findBareToolReferences(prompt, bareNames)
assert.Empty(t, bare,
"sysprompt %s contains tool name(s) without the _kandev suffix: %v — every reference must use the suffixed form",
name, bare)
}
}
// TestFindBareToolReferences_DistinguishesSuffixedFromBare locks in the
// regex-word-boundary contract that findBareToolReferences relies on. If a
// future Go release changes `\b` semantics for `_`, the production tests above
// would silently start passing or failing for the wrong reasons; this unit
// test surfaces the change at its source.
func TestFindBareToolReferences_DistinguishesSuffixedFromBare(t *testing.T) {
bareNames := map[string]struct{}{
"create_task_plan": {},
"list_executors": {},
}
cases := []struct {
name string
prompt string
want []string
}{
{
name: "bare name in prose is flagged",
prompt: "Use list_executors to find IDs.",
want: []string{"list_executors"},
},
{
name: "suffixed name is not flagged",
prompt: "Use list_executors_kandev to find IDs.",
want: nil,
},
{
name: "both forms in same prompt: only bare is flagged",
prompt: "list_executors_kandev (bare: list_executors) returns IDs.",
want: []string{"list_executors"},
},
{
name: "multiple bare names sorted in output",
prompt: "Call create_task_plan then list_executors.",
want: []string{"create_task_plan", "list_executors"},
},
{
name: "no references",
prompt: "Hello world.",
want: nil,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := findBareToolReferences(tc.prompt, bareNames)
assert.Equal(t, tc.want, got)
})
}
}
+1 -1
View File
@@ -117,7 +117,7 @@ AGENT TOOLS:
- delete_agent_profile_kandev: Delete an agent profile. Required: profile_id.
EXECUTOR PROFILE TOOLS:
Executors (local, worktree, local_docker, sprites) are pre-defined. Use list_executors to find executor IDs, then manage profiles.
Executors (local, worktree, local_docker, sprites) are pre-defined. Use list_executors_kandev to find executor IDs, then manage profiles.
- list_executors_kandev: List all executors with their IDs and types.
- list_executor_profiles_kandev: List profiles for an executor. Required: executor_id.
- create_executor_profile_kandev: Create an executor profile. Required: executor_id, name. Optional: mcp_policy, config, prepare_script, cleanup_script.
@@ -45,15 +45,15 @@ test.describe("Config-mode MCP — workflow management", () => {
seedData,
[
'e2e:message("Listing workspaces...")',
"e2e:mcp:kandev:list_workspaces({})",
`e2e:mcp:kandev:list_workflows({"workspace_id":"${seedData.workspaceId}"})`,
"e2e:mcp:kandev:list_workspaces_kandev({})",
`e2e:mcp:kandev:list_workflows_kandev({"workspace_id":"${seedData.workspaceId}"})`,
'e2e:message("Done listing")',
].join("\n"),
);
const page = await runAndWait(testPage, session.task_id, "Done listing");
await expect(page.chat.getByText("list_workspaces")).toBeVisible({ timeout: 10_000 });
await expect(page.chat.getByText("list_workflows")).toBeVisible({ timeout: 10_000 });
await expect(page.chat.getByText("list_workspaces_kandev")).toBeVisible({ timeout: 10_000 });
await expect(page.chat.getByText("list_workflows_kandev")).toBeVisible({ timeout: 10_000 });
});
test("agent can create and list workflow steps", async ({ testPage, apiClient, seedData }) => {
@@ -64,8 +64,8 @@ test.describe("Config-mode MCP — workflow management", () => {
seedData,
[
'e2e:message("Creating step...")',
`e2e:mcp:kandev:create_workflow_step({"workflow_id":"${workflow.id}","name":"QA Review","position":0})`,
`e2e:mcp:kandev:list_workflow_steps({"workflow_id":"${workflow.id}"})`,
`e2e:mcp:kandev:create_workflow_step_kandev({"workflow_id":"${workflow.id}","name":"QA Review","position":0})`,
`e2e:mcp:kandev:list_workflow_steps_kandev({"workflow_id":"${workflow.id}"})`,
'e2e:message("Steps listed")',
].join("\n"),
);
@@ -104,7 +104,7 @@ test.describe("Config-mode MCP — workflow management", () => {
seedData,
[
'e2e:message("Creating step with all fields...")',
`e2e:mcp:kandev:create_workflow_step(${createArgs})`,
`e2e:mcp:kandev:create_workflow_step_kandev(${createArgs})`,
'e2e:message("Step created")',
].join("\n"),
);
@@ -129,7 +129,7 @@ test.describe("Config-mode MCP — workflow management", () => {
seedData,
[
'e2e:message("Creating workflow...")',
`e2e:mcp:kandev:create_workflow({"workspace_id":"${seedData.workspaceId}","name":"E2E Workflow","description":"Created by E2E test"})`,
`e2e:mcp:kandev:create_workflow_kandev({"workspace_id":"${seedData.workspaceId}","name":"E2E Workflow","description":"Created by E2E test"})`,
'e2e:message("Workflow created")',
].join("\n"),
);
@@ -153,7 +153,7 @@ test.describe("Config-mode MCP — workflow management", () => {
seedData,
[
'e2e:message("Updating workflow...")',
`e2e:mcp:kandev:update_workflow({"workflow_id":"${workflow.id}","name":"After Update","description":"Updated description"})`,
`e2e:mcp:kandev:update_workflow_kandev({"workflow_id":"${workflow.id}","name":"After Update","description":"Updated description"})`,
'e2e:message("Workflow updated")',
].join("\n"),
);
@@ -179,7 +179,7 @@ test.describe("Config-mode MCP — workflow management", () => {
seedData,
[
'e2e:message("Deleting workflow...")',
`e2e:mcp:kandev:delete_workflow({"workflow_id":"${workflow.id}"})`,
`e2e:mcp:kandev:delete_workflow_kandev({"workflow_id":"${workflow.id}"})`,
'e2e:message("Workflow deleted")',
].join("\n"),
);
@@ -213,7 +213,7 @@ test.describe("Config-mode MCP — workflow management", () => {
seedData,
[
'e2e:message("Updating step...")',
`e2e:mcp:kandev:update_workflow_step(${updateArgs})`,
`e2e:mcp:kandev:update_workflow_step_kandev(${updateArgs})`,
'e2e:message("Step updated")',
].join("\n"),
);
@@ -247,15 +247,17 @@ test.describe("Config-mode MCP — agent management", () => {
seedData,
[
'e2e:message("Listing agents...")',
"e2e:mcp:kandev:list_agents({})",
`e2e:mcp:kandev:list_agent_profiles({"agent_id":"${agent.id}"})`,
"e2e:mcp:kandev:list_agents_kandev({})",
`e2e:mcp:kandev:list_agent_profiles_kandev({"agent_id":"${agent.id}"})`,
'e2e:message("Agents listed")',
].join("\n"),
);
const page = await runAndWait(testPage, session.task_id, "Agents listed");
await expect(page.chat.getByText("list_agents")).toBeVisible({ timeout: 10_000 });
await expect(page.chat.getByText("list_agent_profiles")).toBeVisible({ timeout: 10_000 });
await expect(page.chat.getByText("list_agents_kandev")).toBeVisible({ timeout: 10_000 });
await expect(page.chat.getByText("list_agent_profiles_kandev")).toBeVisible({
timeout: 10_000,
});
});
test("agent can create and delete an agent profile", async ({
@@ -273,7 +275,7 @@ test.describe("Config-mode MCP — agent management", () => {
seedData,
[
'e2e:message("Creating profile...")',
`e2e:mcp:kandev:create_agent_profile({"agent_id":"${agent.id}","name":"E2E Created Profile","model":"claude-sonnet-4-5-20250514"})`,
`e2e:mcp:kandev:create_agent_profile_kandev({"agent_id":"${agent.id}","name":"E2E Created Profile","model":"claude-sonnet-4-5-20250514"})`,
'e2e:message("Profile created")',
].join("\n"),
);
@@ -296,7 +298,7 @@ test.describe("Config-mode MCP — agent management", () => {
seedData,
[
'e2e:message("Deleting profile...")',
`e2e:mcp:kandev:delete_agent_profile({"profile_id":"${newProfileId}"})`,
`e2e:mcp:kandev:delete_agent_profile_kandev({"profile_id":"${newProfileId}"})`,
'e2e:message("Profile deleted")',
].join("\n"),
);
@@ -319,7 +321,7 @@ test.describe("Config-mode MCP — agent management", () => {
seedData,
[
'e2e:message("Updating agent...")',
`e2e:mcp:kandev:update_agent({"agent_id":"${agent.id}","supports_mcp":true})`,
`e2e:mcp:kandev:update_agent_kandev({"agent_id":"${agent.id}","supports_mcp":true})`,
'e2e:message("Agent updated")',
].join("\n"),
);
@@ -338,7 +340,7 @@ test.describe("Config-mode MCP — agent management", () => {
seedData,
[
'e2e:message("Updating profile...")',
`e2e:mcp:kandev:update_agent_profile({"profile_id":"${seedData.agentProfileId}","name":"Renamed Profile"})`,
`e2e:mcp:kandev:update_agent_profile_kandev({"profile_id":"${seedData.agentProfileId}","name":"Renamed Profile"})`,
'e2e:message("Profile updated")',
].join("\n"),
);
@@ -363,7 +365,7 @@ test.describe("Config-mode MCP — agent management", () => {
seedData,
[
'e2e:message("Updating profile settings...")',
`e2e:mcp:kandev:update_agent_profile({"profile_id":"${seedData.agentProfileId}","model":"claude-sonnet-4-5-20250514","auto_approve":false})`,
`e2e:mcp:kandev:update_agent_profile_kandev({"profile_id":"${seedData.agentProfileId}","model":"claude-sonnet-4-5-20250514","auto_approve":false})`,
'e2e:message("Profile settings updated")',
].join("\n"),
);
@@ -391,15 +393,15 @@ test.describe("Config-mode MCP — MCP server configuration", () => {
seedData,
[
'e2e:message("Reading MCP config...")',
`e2e:mcp:kandev:get_mcp_config({"profile_id":"${seedData.agentProfileId}"})`,
`e2e:mcp:kandev:update_mcp_config({"profile_id":"${seedData.agentProfileId}","enabled":true,"servers":{"test-server":{"command":"node","args":["server.js"]}}})`,
`e2e:mcp:kandev:get_mcp_config_kandev({"profile_id":"${seedData.agentProfileId}"})`,
`e2e:mcp:kandev:update_mcp_config_kandev({"profile_id":"${seedData.agentProfileId}","enabled":true,"servers":{"test-server":{"command":"node","args":["server.js"]}}})`,
'e2e:message("MCP config updated")',
].join("\n"),
);
const page = await runAndWait(testPage, session.task_id, "MCP config updated");
await expect(page.chat.getByText("get_mcp_config")).toBeVisible({ timeout: 10_000 });
await expect(page.chat.getByText("update_mcp_config")).toBeVisible({ timeout: 10_000 });
await expect(page.chat.getByText("get_mcp_config_kandev")).toBeVisible({ timeout: 10_000 });
await expect(page.chat.getByText("update_mcp_config_kandev")).toBeVisible({ timeout: 10_000 });
// Verify via API
const config = await apiClient.getAgentProfileMcpConfig(seedData.agentProfileId);
@@ -425,13 +427,13 @@ test.describe("Config-mode MCP — task management", () => {
seedData,
[
'e2e:message("Listing tasks...")',
`e2e:mcp:kandev:list_tasks({"workflow_id":"${seedData.workflowId}"})`,
`e2e:mcp:kandev:list_tasks_kandev({"workflow_id":"${seedData.workflowId}"})`,
'e2e:message("Tasks listed")',
].join("\n"),
);
const page = await runAndWait(testPage, session.task_id, "Tasks listed");
await expect(page.chat.getByText("list_tasks").first()).toBeVisible({ timeout: 10_000 });
await expect(page.chat.getByText("list_tasks_kandev").first()).toBeVisible({ timeout: 10_000 });
});
test("agent can move a task to a different step", async ({ testPage, apiClient, seedData }) => {
@@ -450,7 +452,7 @@ test.describe("Config-mode MCP — task management", () => {
seedData,
[
'e2e:message("Moving task...")',
`e2e:mcp:kandev:move_task({"task_id":"${task.id}","workflow_id":"${seedData.workflowId}","workflow_step_id":"${targetStep!.id}"})`,
`e2e:mcp:kandev:move_task_kandev({"task_id":"${task.id}","workflow_id":"${seedData.workflowId}","workflow_step_id":"${targetStep!.id}"})`,
'e2e:message("Task moved")',
].join("\n"),
);
@@ -474,7 +476,7 @@ test.describe("Config-mode MCP — task management", () => {
seedData,
[
'e2e:message("Archiving task...")',
`e2e:mcp:kandev:archive_task({"task_id":"${task.id}"})`,
`e2e:mcp:kandev:archive_task_kandev({"task_id":"${task.id}"})`,
'e2e:message("Task archived")',
].join("\n"),
);
@@ -497,7 +499,7 @@ test.describe("Config-mode MCP — task management", () => {
seedData,
[
'e2e:message("Deleting task...")',
`e2e:mcp:kandev:delete_task({"task_id":"${task.id}"})`,
`e2e:mcp:kandev:delete_task_kandev({"task_id":"${task.id}"})`,
'e2e:message("Task deleted")',
].join("\n"),
);
@@ -521,13 +523,13 @@ test.describe("Config-mode MCP — executor management", () => {
seedData,
[
'e2e:message("Listing executors...")',
"e2e:mcp:kandev:list_executors({})",
"e2e:mcp:kandev:list_executors_kandev({})",
'e2e:message("Executors listed")',
].join("\n"),
);
const page = await runAndWait(testPage, session.task_id, "Executors listed");
await expect(page.chat.getByText("list_executors", { exact: true })).toBeVisible({
await expect(page.chat.getByText("list_executors_kandev", { exact: true })).toBeVisible({
timeout: 10_000,
});
});
@@ -547,7 +549,7 @@ test.describe("Config-mode MCP — executor management", () => {
seedData,
[
'e2e:message("Creating executor profile...")',
`e2e:mcp:kandev:create_executor_profile({"executor_id":"${executor.id}","name":"E2E Profile"})`,
`e2e:mcp:kandev:create_executor_profile_kandev({"executor_id":"${executor.id}","name":"E2E Profile"})`,
'e2e:message("Executor profile created")',
].join("\n"),
);
@@ -566,7 +568,7 @@ test.describe("Config-mode MCP — executor management", () => {
seedData,
[
'e2e:message("Deleting executor profile...")',
`e2e:mcp:kandev:delete_executor_profile({"profile_id":"${profile!.id}"})`,
`e2e:mcp:kandev:delete_executor_profile_kandev({"profile_id":"${profile!.id}"})`,
'e2e:message("Executor profile deleted")',
].join("\n"),
);
@@ -597,10 +599,10 @@ test.describe("Config-mode MCP — multi-tool workflow", () => {
seedData,
[
'e2e:message("Starting multi-tool config...")',
"e2e:mcp:kandev:list_workspaces({})",
`e2e:mcp:kandev:list_workflows({"workspace_id":"${seedData.workspaceId}"})`,
`e2e:mcp:kandev:create_workflow_step({"workflow_id":"${workflow.id}","name":"Agent Created Step","position":0})`,
"e2e:mcp:kandev:list_agents({})",
"e2e:mcp:kandev:list_workspaces_kandev({})",
`e2e:mcp:kandev:list_workflows_kandev({"workspace_id":"${seedData.workspaceId}"})`,
`e2e:mcp:kandev:create_workflow_step_kandev({"workflow_id":"${workflow.id}","name":"Agent Created Step","position":0})`,
"e2e:mcp:kandev:list_agents_kandev({})",
'e2e:message("Multi-tool config complete")',
].join("\n"),
);
@@ -628,13 +630,13 @@ test.describe("Config-mode MCP — multi-tool workflow", () => {
[
'e2e:message("Setting up full workflow...")',
// Create 3 steps
`e2e:mcp:kandev:create_workflow_step({"workflow_id":"${workflow.id}","name":"Build","position":0,"color":"#3b82f6"})`,
`e2e:mcp:kandev:create_workflow_step({"workflow_id":"${workflow.id}","name":"Test","position":1,"color":"#eab308"})`,
`e2e:mcp:kandev:create_workflow_step({"workflow_id":"${workflow.id}","name":"Deploy","position":2,"color":"#22c55e"})`,
`e2e:mcp:kandev:create_workflow_step_kandev({"workflow_id":"${workflow.id}","name":"Build","position":0,"color":"#3b82f6"})`,
`e2e:mcp:kandev:create_workflow_step_kandev({"workflow_id":"${workflow.id}","name":"Test","position":1,"color":"#eab308"})`,
`e2e:mcp:kandev:create_workflow_step_kandev({"workflow_id":"${workflow.id}","name":"Deploy","position":2,"color":"#22c55e"})`,
// Create a new agent profile
`e2e:mcp:kandev:create_agent_profile({"agent_id":"${agent.id}","name":"CI Profile","model":"claude-sonnet-4-5-20250514"})`,
`e2e:mcp:kandev:create_agent_profile_kandev({"agent_id":"${agent.id}","name":"CI Profile","model":"claude-sonnet-4-5-20250514"})`,
// Update MCP config on the test profile
`e2e:mcp:kandev:update_mcp_config({"profile_id":"${seedData.agentProfileId}","enabled":true,"servers":{"ci-tools":{"command":"npx","args":["-y","@ci/tools"]}}})`,
`e2e:mcp:kandev:update_mcp_config_kandev({"profile_id":"${seedData.agentProfileId}","enabled":true,"servers":{"ci-tools":{"command":"npx","args":["-y","@ci/tools"]}}})`,
'e2e:message("Full setup complete")',
].join("\n"),
);
+1 -1
View File
@@ -173,7 +173,7 @@ test.describe("Task creation", () => {
const script = [
'e2e:thinking("Analyzing task and creating plan...")',
"e2e:delay(100)",
'e2e:mcp:kandev:create_task_plan({"task_id":"{task_id}","content":"## Plan\\n\\n1. Analyze requirements\\n2. Implement solution\\n3. Write tests","title":"Implementation Plan"})',
'e2e:mcp:kandev:create_task_plan_kandev({"task_id":"{task_id}","content":"## Plan\\n\\n1. Analyze requirements\\n2. Implement solution\\n3. Write tests","title":"Implementation Plan"})',
"e2e:delay(100)",
'e2e:message("I\'ve created an implementation plan for this task.")',
].join("\n");
@@ -12,7 +12,7 @@ import { SessionPage } from "../../pages/session-page";
const PLAN_SCRIPT = [
'e2e:thinking("Creating plan...")',
"e2e:delay(100)",
'e2e:mcp:kandev:create_task_plan({"task_id":"{task_id}","content":"## Plan\\n\\n1. Analyze requirements\\n2. Implement solution\\n3. Write tests","title":"Implementation Plan"})',
'e2e:mcp:kandev:create_task_plan_kandev({"task_id":"{task_id}","content":"## Plan\\n\\n1. Analyze requirements\\n2. Implement solution\\n3. Write tests","title":"Implementation Plan"})',
"e2e:delay(100)",
'e2e:message("Plan created.")',
].join("\n");
+3 -3
View File
@@ -107,7 +107,7 @@ test.describe("MCP subtask creation", () => {
const script = [
'e2e:thinking("Planning subtasks...")',
"e2e:delay(100)",
`e2e:mcp:kandev:create_task({"parent_id":"self","title":"${subtaskTitle}","description":"E2E subtask: verify MCP create_task with parent_id"})`,
`e2e:mcp:kandev:create_task_kandev({"parent_id":"self","title":"${subtaskTitle}","description":"E2E subtask: verify MCP create_task with parent_id"})`,
"e2e:delay(100)",
'e2e:message("Done.")',
].join("\n");
@@ -159,7 +159,7 @@ test.describe("MCP subtask creation", () => {
const script = [
'e2e:thinking("Creating subtask...")',
"e2e:delay(100)",
`e2e:mcp:kandev:create_task({"parent_id":"self","title":"${subtaskTitle}","description":"E2E subtask: verify repository inheritance"})`,
`e2e:mcp:kandev:create_task_kandev({"parent_id":"self","title":"${subtaskTitle}","description":"E2E subtask: verify repository inheritance"})`,
"e2e:delay(100)",
'e2e:message("Done.")',
].join("\n");
@@ -275,7 +275,7 @@ test.describe("Subtask inheritance", () => {
const script = [
'e2e:thinking("Creating subtask...")',
"e2e:delay(100)",
`e2e:mcp:kandev:create_task({"parent_id":"self","title":"${subtaskTitle}","description":"E2E subtask: verify executor and agent profile inheritance"})`,
`e2e:mcp:kandev:create_task_kandev({"parent_id":"self","title":"${subtaskTitle}","description":"E2E subtask: verify executor and agent profile inheritance"})`,
"e2e:delay(100)",
'e2e:message("Done.")',
].join("\n");