从一个真实问题出发
你给 Claude 接入了 Jira 工具,能查工单、创建 Issue、更新状态。代码跑通了,团队很满意。
三周后,另一个团队的同事想在自己的 Agent 项目里也用 Jira 工具。
你有两个选择:
- 把 Jira 工具的代码复制给他
- 让他安装一个 MCP Server,5 分钟接入完成
MCP 的设计目标就是让第二个选项成为默认。
Function Calling 的能力边界
Function Calling(也叫 Tool Use)让 LLM 能够调用外部工具。原理很直接:
python
# 定义工具 schema
tools = [{
"name": "search_jira",
"description": "搜索 Jira 工单",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"},
"project": {"type": "string", "description": "项目 Key,可选"}
},
"required": ["query"]
}
}]
# 调用 LLM,附带工具定义
response = client.messages.create(
model="claude-sonnet-4-6",
tools=tools,
messages=[{"role": "user", "content": "查一下最近的 P0 Bug"}]
)
# 如果 LLM 决定调用工具
if response.stop_reason == "tool_use":
tool_call = response.content[0]
result = search_jira(tool_call.input["query"])
# 把结果返回给 LLM 继续推理
这套机制功能完整,已经在生产环境广泛使用。
但它有一个结构性问题:工具定义和工具实现都在调用侧。
每个用 Jira 工具的项目都需要:
- 维护一份
search_jira的 schema 定义 - 维护一份
search_jira的执行逻辑 - 处理认证、错误重试、结果格式化
三个团队,三份重复的代码。Jira API 更新了,三个地方都要改。
MCP 的核心设计
MCP(Model Context Protocol)把工具定义从调用侧移到了独立的 Server 进程。
vbscript
Function Calling 模型:
┌─────────────────────────────────────┐
│ 你的 Agent 代码 │
│ ├── LLM 调用逻辑 │
│ ├── 工具 schema 定义 │ ← 每个 Agent 自己维护
│ └── 工具执行逻辑(Jira/GitHub/...) │ ← 每个 Agent 自己维护
└─────────────────────────────────────┘
MCP 模型:
┌──────────────────┐ ┌─────────────────────┐
│ Host(Agent/IDE)│────▶│ MCP Server │
│ 只写调用逻辑 │ │ ├── 工具 schema 定义 │ ← 统一维护
└──────────────────┘ │ └── 工具执行逻辑 │ ← 统一维护
└─────────────────────┘
MCP Server 是一个独立运行的进程,通过标准协议(JSON-RPC 2.0)与 Host 通信。任何支持 MCP 的 Host------Claude Desktop、Claude Code、自研 Agent------都可以直接连接这个 Server,无需重新实现工具逻辑。
同一个场景,两种实现
以"搜索 Jira 工单"为例,对比两种方案的完整代码结构。
方案 A:Function Calling
python
# agent.py ------ 工具定义和执行逻辑都在这里
import anthropic
from jira import JIRA
jira_client = JIRA(
server="https://your-company.atlassian.net",
basic_auth=("user@example.com", os.environ["JIRA_API_TOKEN"])
)
# 工具定义(schema)
JIRA_TOOLS = [{
"name": "search_jira",
"description": "搜索 Jira 工单,返回标题、状态、优先级",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"max_results": {"type": "integer", "default": 10}
},
"required": ["query"]
}
}]
# 工具执行逻辑
def execute_tool(name: str, args: dict) -> str:
if name == "search_jira":
issues = jira_client.search_issues(
f'text ~ "{args["query"]}"',
maxResults=args.get("max_results", 10)
)
return "\n".join([
f"[{i.key}] {i.fields.summary} ({i.fields.status.name})"
for i in issues
])
raise ValueError(f"Unknown tool: {name}")
# Agent 主循环
def run_agent(user_input: str):
client = anthropic.Anthropic()
messages = [{"role": "user", "content": user_input}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
tools=JIRA_TOOLS,
messages=messages
)
if response.stop_reason != "tool_use":
return response.content[0].text
tool_call = next(b for b in response.content if b.type == "tool_use")
result = execute_tool(tool_call.name, tool_call.input)
messages.extend([
{"role": "assistant", "content": response.content},
{"role": "user", "content": [{"type": "tool_result",
"tool_use_id": tool_call.id,
"content": result}]}
])
方案 B:MCP Server
python
# jira_mcp_server.py ------ 独立进程,任意 Host 可用
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
from jira import JIRA
jira_client = JIRA(
server="https://your-company.atlassian.net",
basic_auth=("user@example.com", os.environ["JIRA_API_TOKEN"])
)
server = Server("jira-tools")
@server.list_tools()
async def list_tools() -> list[Tool]:
return [Tool(
name="search_jira",
description="搜索 Jira 工单,返回标题、状态、优先级",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string"},
"max_results": {"type": "integer", "default": 10}
},
"required": ["query"]
}
)]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "search_jira":
issues = jira_client.search_issues(
f'text ~ "{arguments["query"]}"',
maxResults=arguments.get("max_results", 10)
)
result = "\n".join([
f"[{i.key}] {i.fields.summary} ({i.fields.status.name})"
for i in issues
])
return [TextContent(type="text", text=result)]
if __name__ == "__main__":
asyncio.run(stdio_server(server))
python
# agent.py ------ 只剩 Agent 逻辑,工具部分消失了
# MCP 的工具发现和调用由框架处理
# 不需要在这里定义任何 Jira 相关的代码
第二个团队接入:
bash
# 安装
pip install jira-mcp-server
# Claude Code 配置(~/.claude/settings.json 或项目 .claude/settings.json)
# 添加 MCP server 配置后,工具自动可用,无需改一行 Agent 代码
核心差异对比
vbscript
维度 Function Calling MCP
──────────────────────────────────────────────────────────────────
工具定义位置 调用侧代码中 独立 Server 进程
复用方式 复制代码 安装 / 连接 Server
跨项目复用 手动同步,容易漂移 Server 版本统一
跨模型使用 每个模型分别配置 一份 Server,任意 Host
状态管理 无(每次调用无状态) Server 可维护连接状态
部署成本 无额外进程 需要管理 Server 进程
适合规模 1-2 个项目使用同一工具 多个项目/团队共享工具
MCP 是 Function Calling 的上层标准化层,两者协同工作。MCP Server 内部的工具调用,仍然通过 Host 与 LLM 之间的 Function Calling 机制完成。
什么时候用 MCP,什么时候 Function Calling 就够
直接用 Function Calling:
- 工具只有一个项目使用,没有复用需求
- 工具逻辑简单,10-20 行代码搞定
- 快速原型,不需要额外维护一个进程
- 工具与 Agent 逻辑高度耦合,分离意义不大
上 MCP Server:
- 工具需要跨多个 Agent 项目复用(Jira / GitHub / 数据库)
- 工具有状态(连接池、缓存、Session)
- 需要让 Claude Desktop / Claude Code 这类 Host 直接使用
- 企业内部统一工具标准,多团队共享
判断规则:
vbscript
这个工具会被超过 2 个项目使用吗?
→ 是 → 考虑 MCP Server
→ 否 → Function Calling 足够
工具需要维护连接状态吗(数据库连接、认证 Session)?
→ 是 → MCP Server(Server 进程管理状态)
→ 否 → 继续下一个问题
非工程师也需要能配置和使用这个工具吗?
→ 是 → MCP Server(参考 Claude Desktop 生态)
→ 否 → Function Calling 就够
总结
MCP 解决的不是"能不能调工具"的问题------Function Calling 已经解决了这个。MCP 解决的是"工具如何标准化复用"的工程问题:把工具从每个 Agent 项目里分离出来,放进独立的 Server,让任何支持 MCP 协议的 Host 都能直接使用。
代价是引入了进程管理的复杂度。收益是工具只维护一份,所有依赖方自动获得更新。
下一篇进入 MCP 协议本身:Host / Client / Server 三层模型,JSON-RPC 消息格式,以及用 MCP Inspector 调试一次真实的通信过程。
参考资料
欢迎访问 PrimeSkills ------ 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。
更多实用知识和有趣产品,欢迎访问我的个人主页