new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

LangChain · Agents · all subjects

agents/tools

147 notes in this subject, read out of this brain and free to use. This is page 2 of 3.

MCP structured content handling

MCP tools can return structured content (machine-parseable data like JSON) alongside human-readable text. When an MCP tool returns structuredContent, the adapter wraps it in an MCPToolArtifact and returns it as the tool's artifact. Access this using the artifact field on the ToolMessage. Structured content is not visible to the model by default but can be appended via interceptors.

MCP multimodal tool content support

MCP tools can return multimodal content (images, text, etc.). When an MCP server returns content with multiple parts, the adapter converts them to LangChain's standard content blocks. Access the standardized representation via the content_blocks property on the ToolMessage.

MCP Resources feature

Resources allow MCP servers to expose data such as files, database records, or API responses that can be read by clients. LangChain converts MCP resources into Blob objects, which provide a unified interface for handling both text and binary content. Use client.get_resources() to load resources from an MCP server.

MCP Prompts feature

Prompts allow MCP servers to expose reusable prompt templates that can be retrieved and used by clients. LangChain converts MCP prompts into messages, making them easy to integrate into chat-based workflows. Use client.get_prompt(server_name, prompt_name) to load a prompt by name, optionally passing arguments as a dictionary.

MCP progress notifications

Subscribe to progress updates for long-running tool executions using the callbacks parameter of MultiServerMCPClient. Define an async on_progress callback with signature (progress: float, total: float | None, message: str | None, context: CallbackContext) -> None.

MCP server logging callback

The MCP protocol supports logging notifications from servers. Use the Callbacks class with on_logging_message callback to handle log messages. The callback receives LoggingMessageNotificationParams (with level and data) and CallbackContext (with server_name).

MCP elicitation feature

Elicitation allows MCP servers to request additional input from users during tool execution. Instead of requiring all inputs upfront, servers can interactively ask for information as needed using ctx.elicit(message, schema).

MCP elicitation response actions

The elicitation callback can return one of three ElicitResult actions: 'accept' (user provided valid input, include data in content field), 'decline' (user chose not to provide information), or 'cancel' (user cancelled the operation entirely).

Example: MultiServerMCPClient with HTTP and stdio transports

```python import asyncio from langchain_mcp_adapters.client import MultiServerMCPClient from langchain.agents import create_agent async def main(): client = MultiServerMCPClient({ "math": { "transport": "stdio", "command": "python", "args": ["/path/to/math_server.py"], }, "weather": { "transport": "http", "url": "http://localhost:8000/mcp", } }) tools = await client.get_tools() agent = create_agent("claude-sonnet-4-6", tools) math_response = await agent.ainvoke( {"messages": [{"role": "user", "content": "what's (3 + 5) x 12?"}]} ) weather_response = await agent.ainvoke( {"messages": [{"role": "user", "content": "what is the weather in nyc?"}]} ) print(math_response) print(weather_response) if __name__ == "__main__": asyncio.run(main()) ``` This example shows accessing multiple MCP servers (math via stdio, weather via HTTP) and using their tools in a LangChain agent.

Example: FastMCP math server with stdio transport

```python from fastmcp import FastMCP mcp = FastMCP("Math") @mcp.tool() def add(a: int, b: int) -> int: """Add two numbers""" return a + b @mcp.tool() def multiply(a: int, b: int) -> int: """Multiply two numbers""" return a * b if __name__ == "__main__": mcp.run(transport="stdio") ``` This example defines a simple FastMCP server with two math tools that communicate via stdio.

Example: FastMCP weather server with HTTP transport

```python from fastmcp import FastMCP mcp = FastMCP("Weather") @mcp.tool() async def get_weather(location: str) -> str: """Get weather for location.""" return "It's always sunny in New York" if __name__ == "__main__": mcp.run(transport="streamable-http") ``` This example defines a FastMCP server with an async weather tool that communicates via HTTP.

Example: MCP stateful session usage

```python from langchain_mcp_adapters.client import MultiServerMCPClient from langchain_mcp_adapters.tools import load_mcp_tools from langchain.agents import create_agent client = MultiServerMCPClient({...}) async with client.session("server_name") as session: tools = await load_mcp_tools(session) agent = create_agent("google_genai:gemini-3.6-flash", tools) ``` This example shows how to create a persistent MCP session for stateful tool usage across multiple invocations.

Example: HTTP transport with headers

```python from langchain_mcp_adapters.client import MultiServerMCPClient from langchain.agents import create_agent client = MultiServerMCPClient({ "weather": { "transport": "http", "url": "http://localhost:8000/mcp", "headers": { "Authorization": "Bearer YOUR_TOKEN", "X-Custom-Header": "custom-value" }, } }) tools = await client.get_tools() agent = create_agent("openai:gpt-5.5", tools) response = await agent.ainvoke({"messages": "what is the weather in nyc?"}) ``` This example shows how to pass custom headers (for authentication or tracing) with HTTP transport.

Example: Loading MCP resources

```python from langchain_mcp_adapters.client import MultiServerMCPClient client = MultiServerMCPClient({...}) # Load all resources from a server blobs = await client.get_resources("server_name") # Or load specific resources by URI blobs = await client.get_resources("server_name", uris=["file:///path/to/file.txt"]) for blob in blobs: print(f"URI: {blob.metadata['uri']}, MIME type: {blob.mimetype}") print(blob.as_string()) ``` This example shows how to load resources from an MCP server.

Example: Loading MCP prompts

```python from langchain_mcp_adapters.client import MultiServerMCPClient client = MultiServerMCPClient({...}) # Load a prompt by name messages = await client.get_prompt("server_name", "summarize") # Load a prompt with arguments messages = await client.get_prompt( "server_name", "code_review", arguments={"language": "python", "focus": "security"} ) # Use the messages in your workflow for message in messages: print(f"{message.type}: {message.content}") ``` This example shows how to load prompts from an MCP server.

Example: Handling MCP elicitation requests

```python from langchain_mcp_adapters.client import MultiServerMCPClient from langchain_mcp_adapters.callbacks import Callbacks, CallbackContext from mcp.shared.context import RequestContext from mcp.types import ElicitRequestParams, ElicitResult async def on_elicitation( mcp_context: RequestContext, params: ElicitRequestParams, context: CallbackContext, ) -> ElicitResult: """Handle elicitation requests from MCP servers.""" return ElicitResult( action="accept", content={"email": "user@example.com", "age": 25}, ) client = MultiServerMCPClient( {"profile": {"url": "http://localhost:8000/mcp", "transport": "http"}}, callbacks=Callbacks(on_elicitation=on_elicitation), ) ``` This example shows how to handle elicitation requests from MCP servers using callbacks.

ToolRuntime parameter for accessing tool_call_id and state

Tool functions can accept a runtime parameter of type ToolRuntime[None, StateType]. This provides access to runtime.tool_call_id (for constructing ToolMessage responses) and the current state. Tools that return Command typically use runtime.tool_call_id when creating the ToolMessage in the update.

Command object structure for state updates

The Command class (from langgraph.types) has an update dict that can contain: messages (list of message objects to add), and any state fields to update (e.g., 'warranty_status', 'current_step'). Multiple fields can be updated in a single Command. The update is applied to state after the tool executes.

Tools drive workflow state transitions via Command

Tools control workflow progression by returning Command objects that update state, including the current_step field. For example, record_warranty_status returns Command(update={'warranty_status': value, 'current_step': 'issue_classifier'}). This makes the workflow explicit and deterministic.

Tool definitions for GitHub agent

GitHub agent has three tools: search_code (searches code in GitHub repositories with query and optional repo parameters), search_issues (searches GitHub issues and pull requests by query), and search_prs (searches pull requests for implementation details by query).

Tool definitions for Notion agent

Notion agent has two tools: search_notion (searches Notion workspace for documentation by query) and get_page (retrieves a specific Notion page by page_id).

Tool definitions for Slack agent

Slack agent has two tools: search_slack (searches Slack messages and threads by query) and get_thread (retrieves a specific Slack thread by thread_id).

SQL assistant skill example: sales_analytics

The sales_analytics skill provides database schema and business logic for sales data analysis. Tables include: customers (customer_id, name, email, signup_date, status, customer_tier), orders (order_id, customer_id, order_date, status, total_amount, sales_region), and order_items (item_id, order_id, product_id, quantity, unit_price, discount_percent). Business logic defines: active customers as status='active' AND signup_date <= CURRENT_DATE - INTERVAL '90 days', revenue calculation counting only orders with status='completed' using total_amount, customer lifetime value (CLV) as sum of all completed order amounts, and high-value orders as total_amount > 1000.

Skills definition structure

Skills are self-contained units of specialized instructions for specific business tasks. Each skill consists of three components: (1) name - a unique identifier for the skill (e.g., 'sales_analytics'), (2) description - a 1-2 sentence description shown in the system prompt to make the skill discoverable without loading full content, and (3) content - the full skill content with detailed instructions, schemas, business logic, and example queries loaded on-demand via tool calls.

load_skill tool pattern

The load_skill tool retrieves full skill content on-demand. It takes a skill_name parameter and returns the complete skill content as a string, which becomes part of the conversation as a ToolMessage. The tool searches through the SKILLS list to find the requested skill and returns its full content, or returns an error message listing available skills if the skill is not found.

Skills vs traditional system prompt approach

Skills loaded via tool calls differ from dynamically changing the system prompt. Instead of modifying the system prompt directly, the agent loads skills through tool invocations. This approach discovers and loads only the skills needed for each task, rather than changing the entire system prompt. The agent sees lightweight skill descriptions in its system prompt and loads full database schemas and business logic through tool calls only when relevant to the user's query.

SQL assistant skill example: inventory_management

The inventory_management skill provides database schema and business logic for inventory tracking. Tables include: products (product_id, product_name, sku, category, unit_cost, reorder_point, discontinued), warehouses (warehouse_id, warehouse_name, location, capacity), inventory (inventory_id, product_id, warehouse_id, quantity_on_hand, last_updated), and stock_movements (movement_id, product_id, warehouse_id, movement_type, quantity, movement_date, reference_number). Business logic defines: available stock as quantity_on_hand > 0, products needing reorder as total quantity_on_hand across all warehouses <= reorder_point, active products as discontinued != true, and stock valuation as quantity_on_hand * unit_cost.

Agent registry with single dispatch tool example (TypeScript)

import { tool, createAgent } from "langchain"; import * as z from "zod"; // Sub-agents developed by different teams const researchAgent = createAgent({ model: "gpt-5.5", prompt: "You are a research specialist...", }); const writerAgent = createAgent({ model: "gpt-5.5", prompt: "You are a writing specialist...", }); // Registry of available sub-agents const SUBAGENTS = { research: researchAgent, writer: writerAgent, }; const task = tool( async ({ agentName, description }) => { const agent = SUBAGENTS[agentName]; const result = await agent.invoke({ messages: [ { role: "user", content: description } ], }); return result.messages.at(-1)?.content; }, { name: "task", description: `Launch an ephemeral subagent. Available agents: - research: Research and fact-finding - writer: Content creation and editing`, schema: z.object({ agentName: z .string() .describe("Name of agent to invoke"), description: z .string() .describe("Task description"), }), } ); // Main coordinator agent const mainAgent = createAgent({ model: "gpt-5.5", tools: [task], prompt: ( "You coordinate specialized sub-agents. " + "Available: research (fact-finding), " + "writer (content creation). " + "Use the task tool to delegate work." ), });

Basic subagent implementation: wrapping as tool

The core mechanism wraps a subagent as a tool that the main agent can call. Create a subagent with create_agent, then wrap it using the @tool decorator with a name and description. The wrapper function invokes the subagent and extracts the content from the final message. The main agent is then created with this wrapped subagent as a tool.

Basic subagent implementation example (Python)

from langchain.tools import tool from langchain.agents import create_agent # Create a subagent subagent = create_agent(model="google_genai:gemini-3.6-flash", tools=[...]) # Wrap it as a tool @tool("research", description="Research a topic and return findings") def call_research_agent(query: str): result = subagent.invoke({"messages": [{"role": "user", "content": query}]}) return result["messages"][-1].content # Main agent with subagent as a tool main_agent = create_agent(model="google_genai:gemini-3.6-flash", tools=[call_research_agent])

Basic subagent implementation example (TypeScript)

import { createAgent, tool } from "langchain"; import { z } from "zod"; // Create a subagent const subagent = createAgent({ model: "google_genai:gemini-3.6-flash", tools: [...] }); // Wrap it as a tool const callResearchAgent = tool( async ({ query }) => { const result = await subagent.invoke({ messages: [{ role: "user", content: query }] }); return result.messages.at(-1)?.content; }, { name: "research", description: "Research a topic and return findings", schema: z.object({ query: z.string() }) } ); // Main agent with subagent as a tool const mainAgent = createAgent({ model: "google_genai:gemini-3.6-flash", tools: [callResearchAgent] });

Subagent tool patterns: tool per agent vs single dispatch

There are two main ways to expose subagents as tools: (1) Tool per agent—best for fine-grained control over each subagent's input/output but requires more setup and customization; (2) Single dispatch tool—best for many agents or distributed teams, using convention over configuration, with simpler composition but less per-agent customization.

Single dispatch tool pattern: convention-based approach

The single dispatch tool approach uses a convention-based single parameterized tool to invoke ephemeral sub-agents for independent tasks. Unlike tool-per-agent where each sub-agent is wrapped as a separate tool, this uses a single 'task' tool where the task description is passed as a human message to the sub-agent and the sub-agent's final message is returned as the tool result. Use this when you want to distribute agent development across multiple teams, need to isolate complex tasks into separate context windows, need a scalable way to add new agents without modifying the coordinator, or prefer convention over customization. This approach trades flexibility in context engineering for simplicity in agent composition and strong context isolation.

Single dispatch tool characteristics

Key characteristics: (1) Single task tool—one parameterized tool that can invoke any registered sub-agent by name; (2) Convention-based invocation—agent selected by name, task passed as human message, final message returned as tool result; (3) Team distribution—different teams can develop and deploy agents independently; (4) Agent discovery—sub-agents can be discovered via system prompt (listing available agents) or through progressive disclosure (loading agent information on-demand via tools).

Agent registry with single dispatch tool example (Python)

from langchain.tools import tool from langchain.agents import create_agent # Sub-agents developed by different teams research_agent = create_agent( model="gpt-5.5", prompt="You are a research specialist..." ) writer_agent = create_agent( model="gpt-5.5", prompt="You are a writing specialist..." ) # Registry of available sub-agents SUBAGENTS = { "research": research_agent, "writer": writer_agent, } @tool def task( agent_name: str, description: str ) -> str: """Launch an ephemeral subagent for a task. Available agents: - research: Research and fact-finding - writer: Content creation and editing """ agent = SUBAGENTS[agent_name] result = agent.invoke({ "messages": [ {"role": "user", "content": description} ] }) return result["messages"][-1].content # Main coordinator agent main_agent = create_agent( model="gpt-5.5", tools=[task], system_prompt=( "You coordinate specialized sub-agents. " "Available: research (fact-finding), " "writer (content creation). " "Use the task tool to delegate work." ), )

OpenAI function calling and LangChain tool calling

In March 2023, OpenAI released function calling in their API, which allowed the API to explicitly generate payloads representing tool calls. Other model providers followed suit, and LangChain was updated to use function calling as the preferred method for tool calling rather than parsing JSON.

Python tool execution and server info example

Example showing how to access execution and server info in a Python tool: from langchain.tools import tool, ToolRuntime @tool def context_aware_tool(runtime: ToolRuntime) -> str: """A tool that uses execution and server info.""" # Access thread and run IDs info = runtime.execution_info print(f"Thread: {info.thread_id}, Run: {info.run_id}") # Access server info (only available on LangGraph Server) server = runtime.server_info if server is not None: print(f"Assistant: {server.assistant_id}") if server.user is not None: print(f"User: {server.user.identity}") return "done" Requires deepagents>=0.5.0 (or langgraph>=1.1.5) for runtime.execution_info and runtime.server_info.

Access runtime in tools using ToolRuntime parameter

In Python tools, use the ToolRuntime parameter to access the Runtime object inside a tool. The ToolRuntime is a generic type that can be parameterized with your Context type: ToolRuntime[Context]. Inside the tool, access runtime.context to get the context object, runtime.store to access long-term memory, and runtime.store.get() to retrieve stored values.

Python tool with runtime context example

Example showing how to access runtime context inside a Python tool: from dataclasses import dataclass from langchain.tools import tool, ToolRuntime @dataclass class Context: user_id: str @tool def fetch_user_email_preferences(runtime: ToolRuntime[Context]) -> str: """Fetch the user's email preferences from the store.""" user_id = runtime.context.user_id preferences: str = "The user prefers you to write a brief and polite email." if runtime.store: if memory := runtime.store.get(("users",), user_id): preferences = memory.value["preferences"] return preferences This demonstrates accessing context from runtime, reading from the store using get(), and returning the value.

JavaScript tool with runtime context example

Example showing how to access runtime context inside a JavaScript tool: import * as z from "zod"; import { tool } from "langchain"; import { type ToolRuntime } from "@langchain/core/tools"; const contextSchema = z.object({ userName: z.string(), }); const fetchUserEmailPreferences = tool( async (_, runtime: ToolRuntime<any, typeof contextSchema>) => { const userName = runtime.context?.userName; if (!userName) { throw new Error("userName is required"); } let preferences = "The user prefers you to write a brief and polite email."; if (runtime.store) { const memory = await runtime.store?.get(["users"], userName); if (memory) { preferences = memory.value.preferences; } } return preferences; }, { name: "fetch_user_email_preferences", description: "Fetch the user's email preferences.", schema: z.object({}), } ); This demonstrates accessing context from runtime and reading from the store.

Access execution info and server info in tools

Inside tools, access execution identity (thread ID, run ID) via runtime.execution_info (Python) or runtime.executionInfo (JavaScript). Access server-specific metadata (assistant ID, authenticated user) via runtime.server_info (Python) or runtime.serverInfo (JavaScript) when running on LangGraph Server. The server_info/serverInfo is None/null when not running on LangGraph Server (e.g., during local development).

JavaScript tool execution and server info example

Example showing how to access execution and server info in a JavaScript tool: import { tool } from "langchain"; import * as z from "zod"; const contextAwareTool = tool( async (_input, runtime) => { // Access thread and run IDs const info = runtime.executionInfo; console.log(`Thread: ${info.threadId}, Run: ${info.runId}`); // Access server info (only available on LangGraph Server) const server = runtime.serverInfo; if (server != null) { console.log(`Assistant: ${server.assistantId}`); if (server.user != null) { console.log(`User: ${server.user.identity}`); } } return "done"; }, { name: "context_aware_tool", description: "A tool that uses execution and server info.", schema: z.object({}), } ); Requires deepagents>=1.9.0 (or @langchain/langgraph>=1.2.8) for runtime.executionInfo and runtime.serverInfo.

Write short-term memory from tools via Command

To modify the agent's short-term memory (state) during execution, return a `Command` with an `update` dict directly from tools. This persists intermediate results or makes information accessible to subsequent tools or prompts. The Command return type allows tools to update agent state.

Access short-term memory in tools with ToolRuntime

Access short-term memory (state) in a tool using the `runtime` parameter typed as `ToolRuntime`. The `runtime` parameter is hidden from the tool signature so the model doesn't see it, but the tool can access the state through `runtime.state`. This allows tools to read and use agent state information.

Tool returns Command for state update example

Example of a tool returning `Command` to update state: ```python @tool def update_user_info( runtime: ToolRuntime[CustomContext, CustomState], ) -> Command: user_id = runtime.context.user_id name = "John Smith" if user_id == "user_123" else "Unknown user" return Command(update={ "user_name": name, "messages": [ToolMessage( "Successfully looked up user information", tool_call_id=runtime.tool_call_id )] }) ```

SQL agent tool: sql_db_query

The sql_db_query tool takes a detailed and correct SQL query as input and returns the result from the database. If the query is not correct, an error message is returned. If an error is returned, the query should be rewritten and checked again. If an Unknown column error is encountered, use sql_db_schema to query the correct table fields.

SQL agent tool: sql_db_list_tables

The sql_db_list_tables tool takes an empty string as input and returns a comma-separated list of tables in the database.

SQL agent tool: sql_db_schema

The sql_db_schema tool takes a comma-separated list of table names as input and outputs the schema and sample rows for those tables. The tool requires that table names actually exist, which can be verified by calling sql_db_list_tables first.

SQL agent tool: sql_db_query_checker

The sql_db_query_checker tool is used to double-check if a query is correct before executing it. This tool should always be used before executing a query with sql_db_query.

fakeModel tool call responses

.respond() supports tool calls by passing an AIMessage with tool_calls array containing objects with properties: name (string), args (object), id (string), and type set to "tool_call". Each tool call object in the array represents one tool invocation.

fakeModel tool call response example

Example of fakeModel tool call responses: import { fakeModel } from "langchain"; import { AIMessage, HumanMessage } from "@langchain/core/messages"; const model = fakeModel() .respond(new AIMessage({ content: "", tool_calls: [ { name: "get_weather", args: { city: "San Francisco" }, id: "call_1", type: "tool_call" }, ], })) .respond(new AIMessage("It's 72°F and sunny in San Francisco.")); const r1 = await model.invoke([new HumanMessage("What's the weather in SF?")]); console.log(r1.tool_calls[0].name); // "get_weather" const r2 = await model.invoke([new HumanMessage("Thanks")]); console.log(r2.content); // "It's 72°F and sunny in San Francisco."

fakeModel respondWithTools shorthand

.respondWithTools() is a shorthand for queuing tool calls without constructing the full AIMessage. Instead of providing an AIMessage with tool_calls, pass an array of objects with name (string), args (object), and optional id (string). If id is omitted, a unique ID is auto-generated. .respond() and .respondWithTools() can be mixed freely in any order.

fakeModel respondWithTools shorthand example

Example of fakeModel .respondWithTools() shorthand: // These two queue entries produce identical responses: model.respond(new AIMessage({ content: "", tool_calls: [ { name: "get_weather", args: { city: "SF" }, id: "call_1", type: "tool_call" }, ], })); // Equivalent shorthand: model.respondWithTools([ { name: "get_weather", args: { city: "SF" }, id: "call_1" }, ]);

fakeModel with bindTools

fakeModel handles bindTools automatically. Agent frameworks like LangChain agents and LangGraph call model.bindTools(tools) internally. The bound model shares the same response queue and call recording as the original, so no special setup is needed.

fakeModel bindTools example

Example of fakeModel with bindTools: import { fakeModel } from "langchain"; import { AIMessage, HumanMessage } from "@langchain/core/messages"; import { tool } from "@langchain/core/tools"; import { z } from "zod"; const searchTool = tool(async ({ query }) => `Results for: ${query}`, { name: "search", description: "Search the web", schema: z.object({ query: z.string() }), }); const model = fakeModel() .respondWithTools([{ name: "search", args: { query: "weather" }, id: "1" }]) .respond(new AIMessage("The weather is sunny.")); const bound = model.bindTools([searchTool]); const r1 = await bound.invoke([new HumanMessage("weather?")]); console.log(r1.tool_calls[0].name); // "search" const r2 = await bound.invoke([new HumanMessage("thanks")]); console.log(r2.content); // "The weather is sunny." // Call recording is shared. Inspect via the original model. console.log(model.callCount); // 2

Agent Chat UI tool message rendering

Agent Chat UI has built-in support for rendering tool calls and tool result messages. This can be customized by hiding specific messages in the chat.

Server-side tool use

Some chat models feature built-in tools (web search, code interpreters) executed server-side by the model provider. Refer to individual chat model integration pages and tool calling documentation for details on enabling these built-in tools.

Tool naming conventions

Prefer snake_case for tool names (e.g., web_search instead of Web Search). Some model providers have issues with or reject names containing spaces or special characters. Sticking to alphanumeric characters, underscores, and hyphens helps improve compatibility across providers.

Custom tool name override

Override the default tool name derived from function name by passing a custom name to the decorator, e.g., @tool('web_search').

Custom tool description

Override the auto-generated tool description by passing a description parameter to the @tool decorator, e.g., @tool('calculator', description='Performs arithmetic calculations.').

Give your agent this brain