Mastering LLM Agent Tool Use: From Function Calling to MCP
What makes an agent an agent is tool use. It is the mechanism that takes LLMs beyond mere text generation and lets them call external APIs, query databases, and execute code.
How Tool Use Works
LLMs only accept text as input and only produce text as output. Tool use bypasses that constraint with the following flow.
1. User message + tool spec → LLM
2. LLM → "Call this tool with these arguments" (JSON output)
3. Runtime executes the actual function
4. Execution result → passed back to the LLM
5. LLM → generates the final natural-language answerThe key point is that the LLM does not execute functions itself. The runtime does, then hands the results back to the LLM.
OpenAI Function Calling
from openai import OpenAI
import json
client = OpenAI()
tools = [
{
"type": "function",
"function": {
"name": "get_stock_price",
"description": "특정 종목의 현재 주가를 조회합니다",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "주식 티커 (예: AAPL, TSLA)"
},
"currency": {
"type": "string",
"enum": ["USD", "KRW"]
}
},
"required": ["symbol"]
}
}
}
]
def get_stock_price(symbol, currency="USD"):
return {"symbol": symbol, "price": 192.5, "currency": currency}
def run_agent(user_message):
messages = [{"role": "user", "content": user_message}]
while True:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto"
)
msg = response.choices[0].message
messages.append(msg)
if msg.tool_calls is None:
return msg.content
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
result = get_stock_price(**args)
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result)
})Anthropic Tool Use
Claude's tool use issues calls as tool_use content blocks and returns results as tool_result.
import anthropic, json
client = anthropic.Anthropic()
tools = [{
"name": "search_docs",
"description": "내부 문서 데이터베이스에서 관련 문서를 검색합니다",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"max_results": {"type": "integer", "default": 5}
},
"required": ["query"]
}
}]
def search_docs(query, max_results=5):
return [{"title": "API 인증 가이드", "content": "Bearer 토큰 방식을 사용합니다..."}]
messages = [{"role": "user", "content": "우리 API 인증 방식이 어떻게 돼?"}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages
)
if response.stop_reason == "end_turn":
text_block = next(b for b in response.content if b.type == "text")
print(text_block.text)
break
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = search_docs(**block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result, ensure_ascii=False)
})
messages.append({"role": "user", "content": tool_results})Model Context Protocol (MCP)
MCP is a tool standardization protocol proposed by Anthropic. It is not tied to any individual LLM SDK; tools are provided independently through a server–client architecture.
# MCP 서버 구현
from mcp.server import Server
import mcp.types as types
server = Server("my-tools-server")
@server.list_tools()
async def handle_list_tools():
return [
types.Tool(
name="query_database",
description="SQL 쿼리를 실행하고 결과를 반환합니다",
inputSchema={
"type": "object",
"properties": {
"sql": {"type": "string", "description": "실행할 SELECT 쿼리"}
},
"required": ["sql"]
}
)
]
@server.call_tool()
async def handle_call_tool(name, arguments):
if name == "query_database":
results = execute_query(arguments["sql"])
return [types.TextContent(type="text", text=str(results))]MCP's advantage is reusability. An MCP server you build once can be connected from Claude Desktop, Claude Code, your own agents, and more.
Tool Design Principles
The difference between a good tool and a bad one is in the description.
| Bad example | Good example |
|---|---|
| "Fetches data" | "Looks up status, amount, and shipping info for a specific order by order ID. Canceled orders can also be retrieved." |
| No parameter descriptions | Explicit type, allowed values, and defaults for each parameter |
The LLM reads the description to choose a tool. If the description is inaccurate, it will call the wrong tool or pass the wrong arguments.
Parallel Tool Calls
Running multiple tools sequentially is slow. Both GPT-4o and Claude support parallel tool calls.
import asyncio
async def execute_tools_parallel(tool_calls):
tasks = [dispatch_tool(tc.function.name, json.loads(tc.function.arguments))
for tc in tool_calls]
return await asyncio.gather(*tasks)In the next installment, we cover memory system design—how to let agents maintain conversation context and carry out long-running tasks.
Editor's Note — From the Field
It feels like attaching more tools should make an agent smarter, but in practice, once you go past five or six tools, the LLM frequently misjudges which tool to use and when. What helped most was cutting the number of tools and writing each description so specifically that a human wouldn't get confused either—search_internal_docs (search the internal wiki) rather than search. Tool design is an extension of prompt engineering.
References
Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
Comments
Be the first to comment.