Skip to main content
Direct tool calls enable you to invoke MCP server tools programmatically without an LLM or agent. This is perfect when you know exactly what tool to call and don’t need AI to make decisions.

When to Use Direct Tool Calls

Use direct tool calls when you need:
  • Deterministic execution: Known tool with known parameters
  • Programmatic control: Integrate MCP tools into existing applications
  • No AI overhead: Simple tool invocation without LLM reasoning
  • Custom workflows: Build your own tool orchestration logic
Sampling Limitation: Direct tool calls don’t support tools that require sampling/completion capabilities, as these need an LLM to generate responses.

Use Cases

Direct tool calls are ideal for:
  • Automation scripts: Scheduled tasks and workflows
  • API wrappers: Simplify MCP tool access
  • Testing: Verify tool behavior programmatically
  • Integration: Connect MCP tools to existing systems

Basic Example

Here’s how to call tools directly using the MCPClient:
import { MCPClient } from 'mcp-use'

async function callToolExample() {
    // Configure the MCP server
    const config = {
        mcpServers: {
            everything: {
                command: 'npx',
                args: ['-y', '@modelcontextprotocol/server-everything']
            }
        }
    }

    // Create client from configuration
    const client = new MCPClient(config)

    try {
        // Initialize all configured sessions
        await client.createAllSessions()

        // Get the session for a specific server
        const session = client.getSession('everything')

        // List available tools
        const tools = await session.listTools()
        const toolNames = tools.map(t => t.name)
        console.log(`Available tools: ${toolNames}`)

        // Call a specific tool with arguments
        const result = await session.callTool(
            'add',
            { a: 1, b: 2 }
        )

        // Handle the result
        if (result.isError) {
            console.error(`Error: ${result.content}`)
        } else {
            console.log(`Tool result: ${result.content}`)
            console.log(`Text result: ${result.content[0].text}`)
        }

    } finally {
        // Clean up resources
        await client.closeAllSessions()
    }
}

callToolExample().catch(console.error)

Working with Tool Results

The call_tool method returns a CallToolResult object with the following attributes:
  • content: A list of ContentBlock objects containing the tool’s output
  • structuredContent: A dictionary with the structured result (for non-sampling tools)
  • isError: Boolean indicating if the tool call encountered an error

Accessing Results

// Call a tool
const result = await session.callTool(
    'get_weather',
    { location: 'San Francisco' }
)

// Check for errors
if (result.isError) {
    console.error(`Tool error: ${result.content}`)
} else {
    // Access text content
    const textResult = result.content[0].text
    console.log(`Weather: ${textResult}`)

    // Access structured content if available
    if (result.structuredContent) {
        const structured = result.structuredContent
        console.log(`Temperature: ${structured.temperature}`)
    }
}

Timeout Configuration

For long-running tools, especially those that trigger sampling or LLM operations, you may need to configure custom timeout settings:
// Extended timeout for long-running operations
const result = await session.callTool(
    'process_large_dataset',
    { dataset: 'large-data.json' },
    {
        timeout: 300000, // 5 minutes
    }
)
Available timeout options:
  • timeout: Request timeout in milliseconds (default: 60000 / 60 seconds)
  • maxTotalTimeout: Maximum total time in milliseconds, even with progress resets
  • resetTimeoutOnProgress: If true, resets the timeout counter when a progress notification is received (default: false)
  • signal: An AbortSignal to programmatically cancel the request
Note: When resetTimeoutOnProgress is enabled and the server sends progress notifications (like ctx.sample() does automatically), the operation can run indefinitely as long as progress is reported regularly.

Multiple Server Example

You can work with multiple MCP servers and call tools from each:
import { MCPClient } from 'mcp-use'

async function multiServerExample() {
    const config = {
        mcpServers: {
            filesystem: {
                command: 'npx',
                args: ['-y', '@modelcontextprotocol/server-filesystem'],
                env: { FILE_PATH: '/tmp' }
            },
            time: {
                command: 'npx',
                args: ['-y', '@modelcontextprotocol/server-time']
            }
        }
    }

    const client = new MCPClient(config)

    try {
        await client.createAllSessions()

        // Call tool from filesystem server
        const fsSession = client.getSession('filesystem')
        const files = await fsSession.callTool(
            'list_files',
            { path: '/tmp' }
        )
        console.log(`Files: ${files.content[0].text}`)

        // Call tool from time server
        const timeSession = client.getSession('time')
        const currentTime = await timeSession.callTool(
            'get_current_time',
            {}
        )
        console.log(`Current time: ${currentTime.content[0].text}`)

    } finally {
        await client.closeAllSessions()
    }
}

multiServerExample().catch(console.error)

Discovering Available Tools

Before calling tools, you may want to discover what’s available:
async function discoverTools() {
    const client = new MCPClient(config)
    await client.createAllSessions()

    try {
        const session = client.getSession('my_server')

        // Get all tools
        const tools = await session.listTools()

        for (const tool of tools) {
            console.log(`Tool: ${tool.name}`)
            console.log(`  Description: ${tool.description}`)

            // Print input schema if available
            if (tool.inputSchema) {
                console.log(`  Parameters: ${JSON.stringify(tool.inputSchema)}`)
            }
            console.log()
        }

    } finally {
        await client.closeAllSessions()
    }
}

Error Handling

Always handle potential errors when making direct tool calls:
async function safeToolCall() {
    try {
        const result = await session.callTool(
            'some_tool',
            { param: 'value' }
        )

        if (result.isError) {
            // Handle tool-specific errors
            const errorMsg = result.content?.[0]?.text || 'Unknown error'
            console.error(`Tool returned error: ${errorMsg}`)
            return null
        }

        return result.content[0].text

    } catch (error) {
        // Handle connection or other errors
        console.error(`Failed to call tool: ${error}`)
        return null
    }
}

Limitations

When using direct tool calls, be aware of these limitations:
  1. No Sampling Support: Tools that require sampling/completion (like text generation) won’t work without an LLM
  2. Manual Tool Selection: You need to know which tool to call - there’s no automatic selection
  3. No Context Management: Unlike agents, direct calls don’t maintain conversation context
  4. Parameter Validation: You’re responsible for providing correct parameters

Complete Example

View the complete working example in the repository: examples/direct_tool_call.py

Next Steps