Complete documentation for future sessions
- CLAUDE.md for AI agents to understand the codebase - GITEA-GUIDE.md centralizes all Gitea operations (API, Registry, Auth) - DEVELOPMENT-WORKFLOW.md explains complete dev process - ROADMAP.md, NEXT-SESSION.md for planning - QUICK-REFERENCE.md, TROUBLESHOOTING.md for daily use - 40+ detailed docs in /docs folder - Backend as submodule from Gitea Everything documented for autonomous operation. Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
788
docs/02-backend/mcp-server.md
Normal file
788
docs/02-backend/mcp-server.md
Normal file
@@ -0,0 +1,788 @@
|
||||
# MCP Server para Agentes
|
||||
|
||||
El MCP (Model Context Protocol) Server es la interfaz que permite a los agentes Claude Code comunicarse con el backend y ejecutar operaciones.
|
||||
|
||||
## Arquitectura MCP
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Claude Code │ MCP Protocol │ MCP Server │
|
||||
│ (Agent Pod) │◄──────────────────►│ (Backend) │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
│
|
||||
┌─────────────────────┼─────────────────────┐
|
||||
│ │ │
|
||||
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
|
||||
│ MySQL │ │ Gitea │ │ K8s │
|
||||
└─────────┘ └─────────┘ └─────────┘
|
||||
```
|
||||
|
||||
## Setup del MCP Server
|
||||
|
||||
```typescript
|
||||
// services/mcp/server.ts
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
||||
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { tools } from './tools'
|
||||
import { handleToolCall } from './handlers'
|
||||
import { logger } from '../../utils/logger'
|
||||
|
||||
export class AgentMCPServer {
|
||||
private server: Server
|
||||
|
||||
constructor() {
|
||||
this.server = new Server(
|
||||
{
|
||||
name: 'aiworker-orchestrator',
|
||||
version: '1.0.0',
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
this.setupHandlers()
|
||||
}
|
||||
|
||||
private setupHandlers() {
|
||||
// List available tools
|
||||
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: tools.map(tool => ({
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
inputSchema: tool.inputSchema,
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
// Handle tool calls
|
||||
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { name, arguments: args } = request.params
|
||||
|
||||
logger.info(`MCP: Tool called: ${name}`, { args })
|
||||
|
||||
try {
|
||||
const result = await handleToolCall(name, args)
|
||||
return result
|
||||
} catch (error) {
|
||||
logger.error(`MCP: Tool error: ${name}`, error)
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: `Error: ${error.message}`
|
||||
}],
|
||||
isError: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async start() {
|
||||
const transport = new StdioServerTransport()
|
||||
await this.server.connect(transport)
|
||||
logger.info('MCP Server started')
|
||||
}
|
||||
}
|
||||
|
||||
// Start MCP server
|
||||
let mcpServer: AgentMCPServer
|
||||
|
||||
export async function startMCPServer() {
|
||||
mcpServer = new AgentMCPServer()
|
||||
await mcpServer.start()
|
||||
return mcpServer
|
||||
}
|
||||
|
||||
export function getMCPServer() {
|
||||
return mcpServer
|
||||
}
|
||||
```
|
||||
|
||||
## Definición de Herramientas
|
||||
|
||||
```typescript
|
||||
// services/mcp/tools.ts
|
||||
import { z } from 'zod'
|
||||
|
||||
export const tools = [
|
||||
{
|
||||
name: 'get_next_task',
|
||||
description: 'Obtiene la siguiente tarea disponible de la cola',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
agentId: {
|
||||
type: 'string',
|
||||
description: 'ID del agente solicitante'
|
||||
},
|
||||
capabilities: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Capacidades del agente (ej: ["javascript", "react"])'
|
||||
}
|
||||
},
|
||||
required: ['agentId']
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'update_task_status',
|
||||
description: 'Actualiza el estado de una tarea',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
taskId: {
|
||||
type: 'string',
|
||||
description: 'ID de la tarea'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['in_progress', 'needs_input', 'ready_to_test', 'completed'],
|
||||
description: 'Nuevo estado'
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Metadata adicional (duración, errores, etc.)'
|
||||
}
|
||||
},
|
||||
required: ['taskId', 'status']
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'ask_user_question',
|
||||
description: 'Solicita información al usuario',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
taskId: {
|
||||
type: 'string',
|
||||
description: 'ID de la tarea'
|
||||
},
|
||||
question: {
|
||||
type: 'string',
|
||||
description: 'Pregunta para el usuario'
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
description: 'Contexto adicional'
|
||||
}
|
||||
},
|
||||
required: ['taskId', 'question']
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'check_question_response',
|
||||
description: 'Verifica si el usuario ha respondido una pregunta',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
taskId: {
|
||||
type: 'string',
|
||||
description: 'ID de la tarea'
|
||||
}
|
||||
},
|
||||
required: ['taskId']
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'create_branch',
|
||||
description: 'Crea una nueva rama en Gitea',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
taskId: {
|
||||
type: 'string',
|
||||
description: 'ID de la tarea'
|
||||
},
|
||||
branchName: {
|
||||
type: 'string',
|
||||
description: 'Nombre de la rama (opcional, se genera automático)'
|
||||
}
|
||||
},
|
||||
required: ['taskId']
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'create_pull_request',
|
||||
description: 'Crea un Pull Request en Gitea',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
taskId: {
|
||||
type: 'string',
|
||||
description: 'ID de la tarea'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'Título del PR'
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Descripción del PR'
|
||||
}
|
||||
},
|
||||
required: ['taskId', 'title', 'description']
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'trigger_preview_deploy',
|
||||
description: 'Despliega un preview environment en K8s',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
taskId: {
|
||||
type: 'string',
|
||||
description: 'ID de la tarea'
|
||||
}
|
||||
},
|
||||
required: ['taskId']
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'get_task_details',
|
||||
description: 'Obtiene detalles completos de una tarea',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
taskId: {
|
||||
type: 'string',
|
||||
description: 'ID de la tarea'
|
||||
}
|
||||
},
|
||||
required: ['taskId']
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'log_activity',
|
||||
description: 'Registra actividad del agente',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
agentId: {
|
||||
type: 'string',
|
||||
description: 'ID del agente'
|
||||
},
|
||||
level: {
|
||||
type: 'string',
|
||||
enum: ['debug', 'info', 'warn', 'error'],
|
||||
description: 'Nivel de log'
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Mensaje'
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Metadata adicional'
|
||||
}
|
||||
},
|
||||
required: ['agentId', 'message']
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'heartbeat',
|
||||
description: 'Envía heartbeat para indicar que el agente está activo',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
agentId: {
|
||||
type: 'string',
|
||||
description: 'ID del agente'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['idle', 'busy', 'error'],
|
||||
description: 'Estado actual'
|
||||
}
|
||||
},
|
||||
required: ['agentId', 'status']
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Implementación de Handlers
|
||||
|
||||
```typescript
|
||||
// services/mcp/handlers.ts
|
||||
import { db } from '../../db/client'
|
||||
import { tasks, agents, taskQuestions, agentLogs } from '../../db/schema'
|
||||
import { eq, and, desc, asc } from 'drizzle-orm'
|
||||
import { GiteaClient } from '../gitea/client'
|
||||
import { K8sClient } from '../kubernetes/client'
|
||||
import { getRedis } from '../../config/redis'
|
||||
import { emitWebSocketEvent } from '../../api/websocket/server'
|
||||
import crypto from 'crypto'
|
||||
|
||||
const giteaClient = new GiteaClient()
|
||||
const k8sClient = new K8sClient()
|
||||
const redis = getRedis()
|
||||
|
||||
export async function handleToolCall(name: string, args: any) {
|
||||
switch (name) {
|
||||
case 'get_next_task':
|
||||
return await getNextTask(args)
|
||||
|
||||
case 'update_task_status':
|
||||
return await updateTaskStatus(args)
|
||||
|
||||
case 'ask_user_question':
|
||||
return await askUserQuestion(args)
|
||||
|
||||
case 'check_question_response':
|
||||
return await checkQuestionResponse(args)
|
||||
|
||||
case 'create_branch':
|
||||
return await createBranch(args)
|
||||
|
||||
case 'create_pull_request':
|
||||
return await createPullRequest(args)
|
||||
|
||||
case 'trigger_preview_deploy':
|
||||
return await triggerPreviewDeploy(args)
|
||||
|
||||
case 'get_task_details':
|
||||
return await getTaskDetails(args)
|
||||
|
||||
case 'log_activity':
|
||||
return await logActivity(args)
|
||||
|
||||
case 'heartbeat':
|
||||
return await heartbeat(args)
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// TOOL IMPLEMENTATIONS
|
||||
// ============================================
|
||||
|
||||
async function getNextTask(args: { agentId: string; capabilities?: string[] }) {
|
||||
const { agentId } = args
|
||||
|
||||
// Get next task from backlog
|
||||
const task = await db.query.tasks.findFirst({
|
||||
where: eq(tasks.state, 'backlog'),
|
||||
with: {
|
||||
project: true
|
||||
},
|
||||
orderBy: [desc(tasks.priority), asc(tasks.createdAt)]
|
||||
})
|
||||
|
||||
if (!task) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({ message: 'No tasks available' })
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
// Assign task to agent
|
||||
await db.update(tasks)
|
||||
.set({
|
||||
state: 'in_progress',
|
||||
assignedAgentId: agentId,
|
||||
assignedAt: new Date(),
|
||||
startedAt: new Date()
|
||||
})
|
||||
.where(eq(tasks.id, task.id))
|
||||
|
||||
await db.update(agents)
|
||||
.set({
|
||||
status: 'busy',
|
||||
currentTaskId: task.id
|
||||
})
|
||||
.where(eq(agents.id, agentId))
|
||||
|
||||
// Emit WebSocket event
|
||||
emitWebSocketEvent('task:status_changed', {
|
||||
taskId: task.id,
|
||||
oldState: 'backlog',
|
||||
newState: 'in_progress',
|
||||
agentId
|
||||
})
|
||||
|
||||
// Cache invalidation
|
||||
await redis.del(`task:${task.id}`)
|
||||
await redis.del(`task:list:${task.projectId}`)
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
task: {
|
||||
id: task.id,
|
||||
title: task.title,
|
||||
description: task.description,
|
||||
priority: task.priority,
|
||||
project: task.project
|
||||
}
|
||||
})
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTaskStatus(args: { taskId: string; status: string; metadata?: any }) {
|
||||
const { taskId, status, metadata } = args
|
||||
|
||||
const updates: any = { state: status }
|
||||
|
||||
if (status === 'completed') {
|
||||
updates.completedAt = new Date()
|
||||
}
|
||||
|
||||
if (metadata?.durationMinutes) {
|
||||
updates.actualDurationMinutes = metadata.durationMinutes
|
||||
}
|
||||
|
||||
await db.update(tasks)
|
||||
.set(updates)
|
||||
.where(eq(tasks.id, taskId))
|
||||
|
||||
// If task completed, free up agent
|
||||
if (status === 'completed' || status === 'ready_to_test') {
|
||||
const task = await db.query.tasks.findFirst({
|
||||
where: eq(tasks.id, taskId)
|
||||
})
|
||||
|
||||
if (task?.assignedAgentId) {
|
||||
await db.update(agents)
|
||||
.set({
|
||||
status: 'idle',
|
||||
currentTaskId: null,
|
||||
tasksCompleted: db.$sql`tasks_completed + 1`
|
||||
})
|
||||
.where(eq(agents.id, task.assignedAgentId))
|
||||
}
|
||||
}
|
||||
|
||||
emitWebSocketEvent('task:status_changed', {
|
||||
taskId,
|
||||
newState: status,
|
||||
metadata
|
||||
})
|
||||
|
||||
await redis.del(`task:${taskId}`)
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({ success: true })
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async function askUserQuestion(args: { taskId: string; question: string; context?: string }) {
|
||||
const { taskId, question, context } = args
|
||||
|
||||
// Update task state
|
||||
await db.update(tasks)
|
||||
.set({ state: 'needs_input' })
|
||||
.where(eq(tasks.id, taskId))
|
||||
|
||||
// Insert question
|
||||
const questionId = crypto.randomUUID()
|
||||
await db.insert(taskQuestions).values({
|
||||
id: questionId,
|
||||
taskId,
|
||||
question,
|
||||
context,
|
||||
status: 'pending'
|
||||
})
|
||||
|
||||
// Notify frontend
|
||||
emitWebSocketEvent('task:needs_input', {
|
||||
taskId,
|
||||
questionId,
|
||||
question,
|
||||
context
|
||||
})
|
||||
|
||||
await redis.del(`task:${taskId}`)
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Question sent to user',
|
||||
questionId
|
||||
})
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async function checkQuestionResponse(args: { taskId: string }) {
|
||||
const { taskId } = args
|
||||
|
||||
const question = await db.query.taskQuestions.findFirst({
|
||||
where: and(
|
||||
eq(taskQuestions.taskId, taskId),
|
||||
eq(taskQuestions.status, 'answered')
|
||||
),
|
||||
orderBy: [desc(taskQuestions.respondedAt)]
|
||||
})
|
||||
|
||||
if (!question || !question.response) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
hasResponse: false,
|
||||
message: 'No response yet'
|
||||
})
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
// Update task back to in_progress
|
||||
await db.update(tasks)
|
||||
.set({ state: 'in_progress' })
|
||||
.where(eq(tasks.id, taskId))
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
hasResponse: true,
|
||||
response: question.response,
|
||||
question: question.question
|
||||
})
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async function createBranch(args: { taskId: string; branchName?: string }) {
|
||||
const { taskId, branchName } = args
|
||||
|
||||
const task = await db.query.tasks.findFirst({
|
||||
where: eq(tasks.id, taskId),
|
||||
with: { project: true }
|
||||
})
|
||||
|
||||
if (!task) {
|
||||
throw new Error('Task not found')
|
||||
}
|
||||
|
||||
const branch = branchName || `task-${taskId.slice(0, 8)}-${task.title.toLowerCase().replace(/\s+/g, '-').slice(0, 30)}`
|
||||
|
||||
// Create branch in Gitea
|
||||
await giteaClient.createBranch(
|
||||
task.project.giteaOwner!,
|
||||
task.project.giteaRepoName!,
|
||||
branch,
|
||||
task.project.defaultBranch!
|
||||
)
|
||||
|
||||
// Update task
|
||||
await db.update(tasks)
|
||||
.set({ branchName: branch })
|
||||
.where(eq(tasks.id, taskId))
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
branchName: branch,
|
||||
repoUrl: task.project.giteaRepoUrl
|
||||
})
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async function createPullRequest(args: { taskId: string; title: string; description: string }) {
|
||||
const { taskId, title, description } = args
|
||||
|
||||
const task = await db.query.tasks.findFirst({
|
||||
where: eq(tasks.id, taskId),
|
||||
with: { project: true }
|
||||
})
|
||||
|
||||
if (!task || !task.branchName) {
|
||||
throw new Error('Task not found or branch not created')
|
||||
}
|
||||
|
||||
const pr = await giteaClient.createPullRequest(
|
||||
task.project.giteaOwner!,
|
||||
task.project.giteaRepoName!,
|
||||
{
|
||||
title,
|
||||
body: description,
|
||||
head: task.branchName,
|
||||
base: task.project.defaultBranch!
|
||||
}
|
||||
)
|
||||
|
||||
await db.update(tasks)
|
||||
.set({
|
||||
prNumber: pr.number,
|
||||
prUrl: pr.html_url
|
||||
})
|
||||
.where(eq(tasks.id, taskId))
|
||||
|
||||
emitWebSocketEvent('task:pr_created', {
|
||||
taskId,
|
||||
prUrl: pr.html_url,
|
||||
prNumber: pr.number
|
||||
})
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
prUrl: pr.html_url,
|
||||
prNumber: pr.number
|
||||
})
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async function triggerPreviewDeploy(args: { taskId: string }) {
|
||||
const { taskId } = args
|
||||
|
||||
const task = await db.query.tasks.findFirst({
|
||||
where: eq(tasks.id, taskId),
|
||||
with: { project: true }
|
||||
})
|
||||
|
||||
if (!task) {
|
||||
throw new Error('Task not found')
|
||||
}
|
||||
|
||||
const previewNamespace = `preview-task-${taskId.slice(0, 8)}`
|
||||
const previewUrl = `https://${previewNamespace}.preview.aiworker.dev`
|
||||
|
||||
// Deploy to K8s
|
||||
await k8sClient.createPreviewDeployment({
|
||||
namespace: previewNamespace,
|
||||
taskId,
|
||||
projectId: task.projectId,
|
||||
image: task.project.dockerImage!,
|
||||
branch: task.branchName!,
|
||||
envVars: task.project.envVars as Record<string, string>
|
||||
})
|
||||
|
||||
await db.update(tasks)
|
||||
.set({
|
||||
state: 'ready_to_test',
|
||||
previewNamespace,
|
||||
previewUrl,
|
||||
previewDeployedAt: new Date()
|
||||
})
|
||||
.where(eq(tasks.id, taskId))
|
||||
|
||||
emitWebSocketEvent('task:ready_to_test', {
|
||||
taskId,
|
||||
previewUrl
|
||||
})
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
previewUrl,
|
||||
namespace: previewNamespace
|
||||
})
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async function getTaskDetails(args: { taskId: string }) {
|
||||
const { taskId } = args
|
||||
|
||||
const task = await db.query.tasks.findFirst({
|
||||
where: eq(tasks.id, taskId),
|
||||
with: {
|
||||
project: true,
|
||||
questions: true
|
||||
}
|
||||
})
|
||||
|
||||
if (!task) {
|
||||
throw new Error('Task not found')
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({ task })
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async function logActivity(args: { agentId: string; level?: string; message: string; metadata?: any }) {
|
||||
const { agentId, level = 'info', message, metadata } = args
|
||||
|
||||
await db.insert(agentLogs).values({
|
||||
agentId,
|
||||
level: level as any,
|
||||
message,
|
||||
metadata
|
||||
})
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({ success: true })
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async function heartbeat(args: { agentId: string; status: string }) {
|
||||
const { agentId, status } = args
|
||||
|
||||
await db.update(agents)
|
||||
.set({
|
||||
lastHeartbeat: new Date(),
|
||||
status: status as any
|
||||
})
|
||||
.where(eq(agents.id, agentId))
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({ success: true })
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Uso desde Claude Code Agent
|
||||
|
||||
Desde el pod del agente, Claude Code usaría las herramientas así:
|
||||
|
||||
```bash
|
||||
# En el pod del agente, configurar MCP
|
||||
# claude-code config add-mcp-server aiworker stdio \
|
||||
# "bun run /app/mcp-client.js"
|
||||
|
||||
# Ejemplo de uso en conversación con Claude Code:
|
||||
# User: "Toma la siguiente tarea y trabaja en ella"
|
||||
# Claude Code internamente llama:
|
||||
# - get_next_task({ agentId: "agent-xyz" })
|
||||
# - Si necesita info: ask_user_question({ taskId: "...", question: "..." })
|
||||
# - Trabaja en el código
|
||||
# - create_branch({ taskId: "..." })
|
||||
# - (commits and pushes)
|
||||
# - create_pull_request({ taskId: "...", title: "...", description: "..." })
|
||||
# - trigger_preview_deploy({ taskId: "..." })
|
||||
# - update_task_status({ taskId: "...", status: "ready_to_test" })
|
||||
```
|
||||
Reference in New Issue
Block a user