diff --git a/src/main/apiServer/routes/agents/handlers/agents.ts b/src/main/apiServer/routes/agents/handlers/agents.ts index 2ceab4ef14..6e35f12c80 100644 --- a/src/main/apiServer/routes/agents/handlers/agents.ts +++ b/src/main/apiServer/routes/agents/handlers/agents.ts @@ -365,11 +365,11 @@ export const updateAgent = async (req: Request, res: Response): Promise => { - try { - const { sessionId } = req.params - logger.info(`Getting session: ${sessionId}`) - - const session = await sessionService.getSessionById(sessionId) - - if (!session) { - logger.warn(`Session not found: ${sessionId}`) - return res.status(404).json({ - error: { - message: 'Session not found', - type: 'not_found', - code: 'session_not_found' - } - }) - } - - logger.info(`Session retrieved successfully: ${sessionId}`) - return res.json(session) - } catch (error: any) { - logger.error('Error getting session:', error) - return res.status(500).json({ - error: { - message: 'Failed to get session', - type: 'internal_error', - code: 'session_get_failed' - } - }) - } -} diff --git a/src/main/apiServer/services/mcp.ts b/src/main/apiServer/services/mcp.ts index f18e662f2e..d6acae745e 100644 --- a/src/main/apiServer/services/mcp.ts +++ b/src/main/apiServer/services/mcp.ts @@ -115,41 +115,6 @@ class MCPApiService extends EventEmitter { logger.info(`Server with id ${id} info:`, { tools: JSON.stringify(tools) }) - // const [version, tools, prompts, resources] = await Promise.all([ - // () => { - // try { - // return client.getServerVersion() - // } catch (error) { - // logger.error(`Failed to get server version for id ${id}:`, { error: error }) - // return '1.0.0' - // } - // }, - // (() => { - // try { - // return client.listTools() - // } catch (error) { - // logger.error(`Failed to list tools for id ${id}:`, { error: error }) - // return [] - // } - // })(), - // (() => { - // try { - // return client.listPrompts() - // } catch (error) { - // logger.error(`Failed to list prompts for id ${id}:`, { error: error }) - // return [] - // } - // })(), - // (() => { - // try { - // return client.listResources() - // } catch (error) { - // logger.error(`Failed to list resources for id ${id}:`, { error: error }) - // return [] - // } - // })() - // ]) - return { id: server.id, name: server.name, diff --git a/src/main/services/agents/BaseService.ts b/src/main/services/agents/BaseService.ts index 8a23cd5643..3f0282d3e4 100644 --- a/src/main/services/agents/BaseService.ts +++ b/src/main/services/agents/BaseService.ts @@ -1,7 +1,8 @@ import { type Client, createClient } from '@libsql/client' import { loggerService } from '@logger' +import { mcpApiService } from '@main/apiServer/services/mcp' import { ModelValidationError, validateModelId } from '@main/apiServer/utils' -import { AgentType, objectKeys, Provider } from '@types' +import { AgentType, MCPTool, objectKeys, Provider, Tool } from '@types' import { drizzle, type LibSQLDatabase } from 'drizzle-orm/libsql' import fs from 'fs' import path from 'path' @@ -10,6 +11,7 @@ import { MigrationService } from './database/MigrationService' import * as schema from './database/schema' import { dbPath } from './drizzle.config' import { AgentModelField, AgentModelValidationError } from './errors' +import { builtinTools } from './services/claudecode/tools' const logger = loggerService.withContext('BaseService') @@ -30,7 +32,7 @@ export abstract class BaseService { protected static db: LibSQLDatabase | null = null protected static isInitialized = false protected static initializationPromise: Promise | null = null - protected jsonFields: string[] = ['built_in_tools', 'mcps', 'configuration', 'accessible_paths', 'allowed_tools'] + protected jsonFields: string[] = ['tools', 'mcps', 'configuration', 'accessible_paths', 'allowed_tools'] /** * Initialize database with retry logic and proper error handling @@ -49,6 +51,31 @@ export abstract class BaseService { return BaseService.initializationPromise } + public async listMcpTools(agentType: AgentType, ids?: string[]): Promise { + const tools: Tool[] = [] + if (agentType === 'claude-code') { + tools.push(...builtinTools) + } + if (ids && ids.length > 0) { + for (const id of ids) { + const server = await mcpApiService.getServerInfo(id) + if (server) { + server.tools.forEach((tool: MCPTool) => { + tools.push({ + id: `mcp_${id}_${tool.name}`, + name: tool.name, + type: 'mcp', + description: tool.description || '', + requirePermissions: true + }) + }) + } + } + } + + return tools + } + private static async performInitialization(): Promise { const maxRetries = 3 let lastError: Error diff --git a/src/main/services/agents/services/AgentService.ts b/src/main/services/agents/services/AgentService.ts index a5c1b3d96d..78e7acadb8 100644 --- a/src/main/services/agents/services/AgentService.ts +++ b/src/main/services/agents/services/AgentService.ts @@ -16,7 +16,6 @@ import { count, eq } from 'drizzle-orm' import { BaseService } from '../BaseService' import { type AgentRow, agentsTable, type InsertAgentRow } from '../database/schema' import { AgentModelField } from '../errors' -import { builtinTools } from './claudecode/tools' export class AgentService extends BaseService { private static instance: AgentService | null = null @@ -92,10 +91,7 @@ export class AgentService extends BaseService { } const agent = this.deserializeJsonFields(result[0]) as GetAgentResponse - if (agent.type === 'claude-code') { - agent.built_in_tools = builtinTools - } - + agent.tools = await this.listMcpTools(agent.type, agent.mcps) return agent } @@ -115,11 +111,9 @@ export class AgentService extends BaseService { const agents = result.map((row) => this.deserializeJsonFields(row)) as GetAgentResponse[] - agents.forEach((agent) => { - if (agent.type === 'claude-code') { - agent.built_in_tools = builtinTools - } - }) + for (const agent of agents) { + agent.tools = await this.listMcpTools(agent.type, agent.mcps) + } return { agents, total: totalResult[0].count } } diff --git a/src/main/services/agents/services/SessionService.ts b/src/main/services/agents/services/SessionService.ts index 2e70de4b11..7495a99d8e 100644 --- a/src/main/services/agents/services/SessionService.ts +++ b/src/main/services/agents/services/SessionService.ts @@ -3,7 +3,6 @@ import { type AgentEntity, type AgentSessionEntity, type CreateSessionRequest, - type CreateSessionResponse, type GetAgentSessionResponse, type ListOptions, type UpdateSessionRequest, @@ -30,7 +29,7 @@ export class SessionService extends BaseService { await BaseService.initialize() } - async createSession(agentId: string, req: CreateSessionRequest): Promise { + async createSession(agentId: string, req: CreateSessionRequest): Promise { this.ensureInitialized() // Validate agent exists - we'll need to import AgentService for this check @@ -89,7 +88,8 @@ export class SessionService extends BaseService { throw new Error('Failed to create session') } - return this.deserializeJsonFields(result[0]) as AgentSessionEntity + const session = this.deserializeJsonFields(result[0]) + return await this.getSession(agentId, session.id) } async getSession(agentId: string, id: string): Promise { @@ -106,21 +106,7 @@ export class SessionService extends BaseService { } const session = this.deserializeJsonFields(result[0]) as GetAgentSessionResponse - - return session - } - - async getSessionById(id: string): Promise { - this.ensureInitialized() - - const result = await this.database.select().from(sessionsTable).where(eq(sessionsTable.id, id)).limit(1) - - if (!result[0]) { - return null - } - - const session = this.deserializeJsonFields(result[0]) as GetAgentSessionResponse - + session.tools = await this.listMcpTools(session.agent_type, session.mcps) return session } diff --git a/src/main/services/agents/services/claudecode/tools.ts b/src/main/services/agents/services/claudecode/tools.ts index 96dd0eeb28..0785827cd5 100644 --- a/src/main/services/agents/services/claudecode/tools.ts +++ b/src/main/services/agents/services/claudecode/tools.ts @@ -2,47 +2,83 @@ import { Tool } from '@types' // https://docs.anthropic.com/en/docs/claude-code/settings#tools-available-to-claude export const builtinTools: Tool[] = [ - { id: 'Bash', name: 'Bash', description: 'Executes shell commands in your environment', requirePermissions: true }, - { id: 'Edit', name: 'Edit', description: 'Makes targeted edits to specific files', requirePermissions: true }, - { id: 'Glob', name: 'Glob', description: 'Finds files based on pattern matching', requirePermissions: false }, - { id: 'Grep', name: 'Grep', description: 'Searches for patterns in file contents', requirePermissions: false }, + { + id: 'Bash', + name: 'Bash', + description: 'Executes shell commands in your environment', + requirePermissions: true, + type: 'builtin' + }, + { + id: 'Edit', + name: 'Edit', + description: 'Makes targeted edits to specific files', + requirePermissions: true, + type: 'builtin' + }, + { + id: 'Glob', + name: 'Glob', + description: 'Finds files based on pattern matching', + requirePermissions: false, + type: 'builtin' + }, + { + id: 'Grep', + name: 'Grep', + description: 'Searches for patterns in file contents', + requirePermissions: false, + type: 'builtin' + }, { id: 'MultiEdit', name: 'MultiEdit', description: 'Performs multiple edits on a single file atomically', - requirePermissions: true + requirePermissions: true, + type: 'builtin' }, { id: 'NotebookEdit', name: 'NotebookEdit', description: 'Modifies Jupyter notebook cells', - requirePermissions: true + requirePermissions: true, + type: 'builtin' }, { id: 'NotebookRead', name: 'NotebookRead', description: 'Reads and displays Jupyter notebook contents', - requirePermissions: false + requirePermissions: false, + type: 'builtin' }, - { id: 'Read', name: 'Read', description: 'Reads the contents of files', requirePermissions: false }, + { id: 'Read', name: 'Read', description: 'Reads the contents of files', requirePermissions: false, type: 'builtin' }, { id: 'Task', name: 'Task', description: 'Runs a sub-agent to handle complex, multi-step tasks', - requirePermissions: false + requirePermissions: false, + type: 'builtin' }, { id: 'TodoWrite', name: 'TodoWrite', description: 'Creates and manages structured task lists', - requirePermissions: false + requirePermissions: false, + type: 'builtin' + }, + { + id: 'WebFetch', + name: 'WebFetch', + description: 'Fetches content from a specified URL', + requirePermissions: true, + type: 'builtin' }, - { id: 'WebFetch', name: 'WebFetch', description: 'Fetches content from a specified URL', requirePermissions: true }, { id: 'WebSearch', name: 'WebSearch', description: 'Performs web searches with domain filtering', - requirePermissions: true + requirePermissions: true, + type: 'builtin' }, - { id: 'Write', name: 'Write', description: 'Creates or overwrites files', requirePermissions: true } + { id: 'Write', name: 'Write', description: 'Creates or overwrites files', requirePermissions: true, type: 'builtin' } ] diff --git a/src/renderer/src/api/agent.ts b/src/renderer/src/api/agent.ts index 088a895ff0..b453f116cd 100644 --- a/src/renderer/src/api/agent.ts +++ b/src/renderer/src/api/agent.ts @@ -11,11 +11,10 @@ import { CreateAgentResponseSchema, CreateSessionForm, CreateSessionRequest, - CreateSessionResponse, - CreateSessionResponseSchema, GetAgentResponse, GetAgentResponseSchema, GetAgentSessionResponse, + GetAgentSessionResponseSchema, ListAgentSessionsResponse, ListAgentSessionsResponseSchema, type ListAgentsResponse, @@ -27,9 +26,7 @@ import { UpdateAgentResponse, UpdateAgentResponseSchema, UpdateSessionForm, - UpdateSessionRequest, - UpdateSessionResponse, - UpdateSessionResponseSchema + UpdateSessionRequest } from '@types' import axios, { Axios, AxiosRequestConfig, isAxiosError } from 'axios' import { ZodError } from 'zod' @@ -172,12 +169,12 @@ export class AgentApiClient { } } - public async createSession(agentId: string, session: CreateSessionForm): Promise { + public async createSession(agentId: string, session: CreateSessionForm): Promise { const url = this.getSessionPaths(agentId).base try { const payload = session satisfies CreateSessionRequest const response = await this.axios.post(url, payload) - const data = CreateSessionResponseSchema.parse(response.data) + const data = GetAgentSessionResponseSchema.parse(response.data) return data } catch (error) { throw processError(error, 'Failed to add session.') @@ -209,12 +206,12 @@ export class AgentApiClient { } } - public async updateSession(agentId: string, session: UpdateSessionForm): Promise { + public async updateSession(agentId: string, session: UpdateSessionForm): Promise { const url = this.getSessionPaths(agentId).withId(session.id) try { const payload = session satisfies UpdateSessionRequest const response = await this.axios.patch(url, payload) - const data = UpdateSessionResponseSchema.parse(response.data) + const data = GetAgentSessionResponseSchema.parse(response.data) if (session.id !== data.id) { throw new Error('Session ID mismatch in response') } diff --git a/src/renderer/src/types/agent.ts b/src/renderer/src/types/agent.ts index 74b2fef4de..0c7b8c0675 100644 --- a/src/renderer/src/types/agent.ts +++ b/src/renderer/src/types/agent.ts @@ -35,6 +35,7 @@ export const isAgentType = (type: unknown): type is AgentType => { export const ToolSchema = z.object({ id: z.string(), name: z.string(), + type: z.enum(['builtin', 'mcp', 'custom']), description: z.string().optional(), requirePermissions: z.boolean().optional() }) @@ -201,7 +202,7 @@ export interface UpdateAgentRequest extends Partial {} export type ReplaceAgentRequest = AgentBase export const GetAgentResponseSchema = AgentEntitySchema.extend({ - built_in_tools: z.array(ToolSchema).optional() // Built-in tools available to the agent + tools: z.array(ToolSchema).optional() // All tools available to the agent (including built-in and custom) }) export type GetAgentResponse = z.infer @@ -224,7 +225,7 @@ export type CreateSessionRequest = z.infer export interface UpdateSessionRequest extends Partial {} export const GetAgentSessionResponseSchema = AgentSessionEntitySchema.extend({ - built_in_tools: z.array(ToolSchema).optional(), // Built-in tools available to the agent + tools: z.array(ToolSchema).optional(), // All tools available to the session (including built-in and custom) messages: z.array(AgentSessionMessageEntitySchema).optional() // Messages in the session }) @@ -241,12 +242,6 @@ export type ListAgentSessionsResponse = z.infer -export const CreateSessionResponseSchema = AgentSessionEntitySchema - -export type CreateSessionResponse = AgentSessionEntity - -export const UpdateSessionResponseSchema = GetAgentSessionResponseSchema - export type UpdateSessionResponse = GetAgentSessionResponse export const AgentServerErrorSchema = z.object({