Skip to main content
Glama

add_mcp_tool_to_letta

Register an MCP tool from a connected server and attach it to a specific agent in the Letta system for expanded functionality.

Instructions

Registers a tool from a connected MCP server as a native Letta tool AND attaches it to a specified agent. Use list_mcp_tools_by_server to find available tools, and list_agents to get agent IDs.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
tool_nameYesThe name of the MCP tool to find, register, and attach.
agent_idYesThe ID of the agent to attach the newly registered tool to.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
successYes
tool_idNo
agent_idNo
tool_nameYes
attached_to_agentNo

Implementation Reference

  • The primary handler function that implements the core logic of the 'add_mcp_tool_to_letta' tool. It validates inputs, locates the MCP server for the tool, registers the tool as a native Letta tool via API calls, attaches it to the specified agent, and returns structured results.
    export async function handleAddMcpToolToLetta(server, args) {
        try {
            // Validate arguments (new arguments: tool_name, agent_id)
            if (!args.tool_name) {
                throw new Error('Missing required argument: tool_name (MCP tool name)');
            }
            if (!args.agent_id) {
                throw new Error('Missing required argument: agent_id');
            }
    
            const { tool_name, agent_id } = args;
            const mcp_tool_name = tool_name;
    
            // Headers for API requests
            const headers = server.getApiHeaders();
    
            // --- Find the MCP Server Name for the given tool_name ---
            logger.info(`Searching for MCP server providing tool: ${mcp_tool_name}...`);
            let mcp_server_name = null;
            const serversResponse = await server.api.get('/tools/mcp/servers', { headers });
            if (!serversResponse.data || typeof serversResponse.data !== 'object') {
                throw new Error('Failed to list MCP servers or invalid response format.');
            }
            const serverNames = Object.keys(serversResponse.data);
    
            for (const serverName of serverNames) {
                logger.info(`Checking server: ${serverName}`);
                try {
                    const toolsResponse = await server.api.get(
                        `/tools/mcp/servers/${serverName}/tools`,
                        { headers },
                    );
                    if (toolsResponse.data && Array.isArray(toolsResponse.data)) {
                        const foundTool = toolsResponse.data.find(
                            (tool) => tool.name === mcp_tool_name,
                        );
                        if (foundTool) {
                            mcp_server_name = serverName;
                            logger.info(
                                `Found tool '${mcp_tool_name}' on server '${mcp_server_name}'.`,
                            );
                            break; // Stop searching once found
                        }
                    }
                } catch (toolListError) {
                    // Log error but continue searching other servers
                    logger.info(
                        `Could not list tools for server ${serverName}: ${toolListError.message}`,
                    );
                }
            }
    
            if (!mcp_server_name) {
                throw new Error(
                    `Could not find any MCP server providing the tool named '${mcp_tool_name}'.`,
                );
            }
            // --- End of Find MCP Server Name ---
    
            // Headers for API requests
            // Note: The API spec didn't explicitly require user_id for this endpoint,
            // but adding it might be necessary depending on Letta's auth setup.
    
            logger.info(
                `Attempting to register MCP tool ${mcp_server_name}/${mcp_tool_name} with Letta...`,
            );
            const registerUrl = `/tools/mcp/servers/${mcp_server_name}/${mcp_tool_name}`;
    
            // Make the POST request to register the tool
            logger.info(`DEBUG: Calling registration URL: POST ${registerUrl}`);
            const registerResponse = await server.api.post(registerUrl, {}, { headers });
    
            // Check registration response data for success and the new tool ID
            if (!registerResponse.data || !registerResponse.data.id) {
                throw new Error(
                    `Registration API call succeeded but did not return the expected tool ID. Response: ${JSON.stringify(registerResponse.data)}`,
                );
            }
    
            const lettaToolId = registerResponse.data.id;
            const lettaToolName = registerResponse.data.name || mcp_tool_name;
            logger.info(`Successfully registered tool. Letta Tool ID: ${lettaToolId}`);
    
            // Now, attempt to attach the newly registered tool to the agent
            logger.info(`Attempting to attach tool ${lettaToolId} to agent ${agent_id}...`);
            const attachUrl = `/agents/${agent_id}/tools/attach/${lettaToolId}`;
            let attachSuccess = false;
            let attachMessage = '';
            let attachError = null;
    
            try {
                // Use specific headers for the agent context if needed, otherwise reuse
                const attachHeaders = { ...headers };
    
                logger.info(`DEBUG: Calling attachment URL: PATCH ${attachUrl}`);
                const attachResponse = await server.api.patch(
                    attachUrl,
                    {},
                    { headers: attachHeaders },
                );
    
                // Verify attachment (optional but good practice)
                const attachedToolIds = attachResponse.data.tools?.map((t) => t.id) || [];
                if (attachedToolIds.includes(lettaToolId)) {
                    attachSuccess = true;
                    attachMessage = `Successfully attached tool '${lettaToolName}' (ID: ${lettaToolId}) to agent ${agent_id}.`;
                    logger.info(attachMessage);
                } else {
                    attachMessage = `Attachment API call succeeded, but tool ${lettaToolId} was not found in agent's tools list afterwards.`;
                    logger.info(attachMessage);
                    // Consider this a partial failure
                }
            } catch (error) {
                attachSuccess = false;
                attachMessage = `Failed to attach tool ${lettaToolId} to agent ${agent_id}.`;
                attachError = error.response?.data || error.message || error;
                logger.error(`${attachMessage} Error:`, attachError);
            }
    
            // Return combined result
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify({
                            letta_tool_id: lettaToolId,
                            letta_tool_name: lettaToolName,
                            agent_id: agent_id,
                            attached: attachSuccess,
                            mcp_server_name: mcp_server_name,
                            mcp_tool_name: mcp_tool_name,
                            ...(attachError ? { error: attachError } : {}),
                        }),
                    },
                ],
                isError: !attachSuccess, // Consider it an error if attachment failed
            };
        } catch (error) {
            logger.error(`Error during MCP tool registration or attachment: ${error.message}`);
            // Ensure the error response includes context about which step failed if possible
            server.createErrorResponse(
                error,
                `Failed during registration/attachment of ${args.mcp_server_name || 'unknown_server'}/${args.mcp_tool_name || 'unknown_tool'}`,
            );
        }
    }
  • The tool definition including name, description, and input schema for validation.
    export const addMcpToolToLettaDefinition = {
        name: 'add_mcp_tool_to_letta',
        description:
            'Registers a tool from a connected MCP server as a native Letta tool AND attaches it to a specified agent. Use list_mcp_tools_by_server to find available tools, and list_agents to get agent IDs.',
        inputSchema: {
            type: 'object',
            properties: {
                tool_name: {
                    type: 'string',
                    description: 'The name of the MCP tool to find, register, and attach.',
                },
                agent_id: {
                    type: 'string',
                    description: 'The ID of the agent to attach the newly registered tool to.',
                },
            },
            required: ['tool_name', 'agent_id'], // Updated required fields
        },
    };
  • Registration of the tool handler in the central switch statement for dispatching tool calls within the MCP server request handler.
    case 'add_mcp_tool_to_letta':
        return handleAddMcpToolToLetta(server, request.params.arguments);
  • Import of the handler and definition from the implementation file into the central tools index for registration.
        handleAddMcpToolToLetta,
        addMcpToolToLettaDefinition,
    } from './mcp/add-mcp-tool-to-letta.js';
  • Output schema defining the expected structured response format for the tool.
    add_mcp_tool_to_letta: {
        type: 'object',
        properties: {
            success: { type: 'boolean' },
            tool_name: { type: 'string' },
            tool_id: { type: 'string' },
            attached_to_agent: { type: 'boolean' },
            agent_id: { type: 'string' },
        },
        required: ['success', 'tool_name'],
    },
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds useful context about the two-step process (registration + attachment) and prerequisites, but annotations are minimal (only a title), so it carries most of the behavioral burden. It doesn't disclose potential side effects like whether registration is persistent, if duplicates are handled, or error conditions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with zero waste: the first states the purpose and dual action, the second provides prerequisite tools. Every word earns its place, and it's front-loaded with the core functionality.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (registration + attachment), minimal annotations, but 100% schema coverage and an output schema (which handles return values), the description is mostly complete. It could improve by mentioning error cases or idempotency, but the workflow guidance and clear purpose make it strong.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema description coverage, the schema already documents both parameters well. The description adds no additional parameter semantics beyond what's in the schema, so it meets the baseline of 3 without compensating for gaps.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('registers a tool from a connected MCP server as a native Letta tool AND attaches it to a specified agent'), identifies the resource (MCP tool), and distinguishes it from siblings like 'attach_tool' (which likely attaches existing tools) and 'upload_tool' (which may upload custom tools).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly provides when-to-use guidance by naming two alternative tools ('list_mcp_tools_by_server to find available tools, and list_agents to get agent IDs') that should be used first to gather required information, creating a clear workflow context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/oculairmedia/Letta-MCP-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server