Skip to content

Commit 03b1ec8

Browse files
Add mcp-vs-api-calls: sample code for MCP vs API Calls tutorial (#816)
* Tool: Add function to get package info from PyPI This function retrieves package information from PyPI, including the name, version, and summary. * MCP vs API Calls Python LLM Apps Materials Materials attached to the "MCP vs API Calls: Which Should You Use for Python LLM Apps?" tutorial * Create README.md for MCP vs API Calls tutorial Add README for MCP vs API Calls tutorial with setup instructions and example usage. --------- Co-authored-by: stephengruppetta <51741022+stephengruppetta@users.noreply.github.com>
1 parent de6f46e commit 03b1ec8

5 files changed

Lines changed: 234 additions & 0 deletions

File tree

‎mcp-vs-api-calls/README.md‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# MCP vs API Calls: Which Should You Use for Python LLM Apps?
2+
3+
This folder contains the sample code for the Real Python tutorial [MCP vs API Calls: Which Should You Use for Python LLM Apps?](https://realpython.com/mcp-vs-api-calls/).
4+
5+
## Files
6+
7+
- `tool.py`: the PyPI lookup function that both approaches share, written with the standard library only.
8+
- `client_api.py`: the direct API calls integration, which declares the tool schema by hand.
9+
- `server.py`: the MCP server, which exposes the tool through the `mcp` SDK.
10+
- `client.py`: the MCP client, which discovers and calls the tool through the server.
11+
12+
## Setup
13+
14+
Create a virtual environment and install the dependencies:
15+
16+
```console
17+
$ python -m venv tools-venv
18+
$ source tools-venv/bin/activate
19+
(tools-venv) $ python -m pip install anthropic "mcp>=2,<3"
20+
```
21+
22+
The examples use the Anthropic client, which requires an API key. Create a key in the [Anthropic Console](https://console.anthropic.com/), scope it to a single workspace, and export it:
23+
24+
```console
25+
(tools-venv) $ export ANTHROPIC_API_KEY="your-api-key-here"
26+
```
27+
28+
## Running the examples
29+
30+
Run the direct API calls version:
31+
32+
```console
33+
(tools-venv) $ python client_api.py
34+
The latest version of the Jinja2 package is 3.1.6.
35+
```
36+
37+
Run the MCP version:
38+
39+
```console
40+
(tools-venv) $ python client.py
41+
The latest version of the Jinja2 package is 3.1.6.
42+
```
43+
44+
You only run `client.py`. It launches `server.py` as a subprocess and talks to it over standard I/O, so you never start the server yourself.

‎mcp-vs-api-calls/client.py‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import asyncio
2+
import sys
3+
4+
import anthropic
5+
from anthropic.types import Message, ToolUseBlock
6+
from mcp import Client, StdioServerParameters
7+
8+
MODEL = "claude-sonnet-5"
9+
QUESTION = (
10+
"What is the latest version of the Jinja2 package? "
11+
"Answer in one short plain-text sentence."
12+
)
13+
14+
15+
async def discover_tools(client: Client) -> list[dict]:
16+
discovered = await client.list_tools()
17+
return [
18+
{
19+
"name": tool.name,
20+
"description": tool.description,
21+
"input_schema": tool.input_schema,
22+
}
23+
for tool in discovered.tools
24+
]
25+
26+
27+
def ask_model(
28+
client: anthropic.Anthropic,
29+
messages: list[dict],
30+
tools: list[dict],
31+
model: str = MODEL,
32+
) -> Message:
33+
return client.messages.create(
34+
model=model,
35+
max_tokens=1024,
36+
tools=tools,
37+
messages=messages,
38+
)
39+
40+
41+
async def run_tool(client: Client, block: ToolUseBlock) -> str:
42+
result = await client.call_tool(block.name, block.input)
43+
return result.content[0].text
44+
45+
46+
def tool_result(block: ToolUseBlock, output: str) -> dict:
47+
return {
48+
"role": "user",
49+
"content": [
50+
{
51+
"type": "tool_result",
52+
"tool_use_id": block.id,
53+
"content": output,
54+
},
55+
],
56+
}
57+
58+
59+
async def main(server: StdioServerParameters) -> None:
60+
async with Client(server) as mcp:
61+
tools = await discover_tools(mcp)
62+
63+
client = anthropic.Anthropic()
64+
messages = [{"role": "user", "content": QUESTION}]
65+
response = ask_model(client, messages, tools)
66+
for block in response.content:
67+
if block.type == "tool_use":
68+
messages.append(
69+
{"role": "assistant", "content": response.content}
70+
)
71+
output = await run_tool(mcp, block)
72+
messages.append(tool_result(block, output))
73+
final = ask_model(client, messages, tools)
74+
print(final.content[0].text)
75+
76+
77+
if __name__ == "__main__":
78+
server = StdioServerParameters(
79+
command=sys.executable,
80+
args=["server.py"],
81+
)
82+
asyncio.run(main(server))

‎mcp-vs-api-calls/client_api.py‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import json
2+
3+
import anthropic
4+
from anthropic.types import Message, ToolUseBlock
5+
6+
from tool import get_package_info
7+
8+
MODEL = "claude-sonnet-5"
9+
QUESTION = (
10+
"What is the latest version of the Jinja2 package? "
11+
"Answer in one short plain-text sentence."
12+
)
13+
14+
TOOLS = [
15+
{
16+
"name": "get_package_info",
17+
"description": (
18+
"Look up a package on PyPI and return its "
19+
"latest version and summary."
20+
),
21+
"input_schema": {
22+
"type": "object",
23+
"properties": {
24+
"package_name": {
25+
"type": "string",
26+
"description": "The package name on PyPI.",
27+
},
28+
},
29+
"required": ["package_name"],
30+
},
31+
},
32+
]
33+
34+
35+
def ask_model(
36+
client: anthropic.Anthropic,
37+
messages: list[dict],
38+
tools: list[dict],
39+
model: str = MODEL,
40+
) -> Message:
41+
return client.messages.create(
42+
model=model,
43+
max_tokens=1024,
44+
tools=tools,
45+
messages=messages,
46+
)
47+
48+
49+
def run_tool(block: ToolUseBlock) -> str:
50+
result = get_package_info(**block.input)
51+
return json.dumps(result)
52+
53+
54+
def tool_result(block: ToolUseBlock, output: str) -> dict:
55+
return {
56+
"role": "user",
57+
"content": [
58+
{
59+
"type": "tool_result",
60+
"tool_use_id": block.id,
61+
"content": output,
62+
},
63+
],
64+
}
65+
66+
67+
def main() -> None:
68+
client = anthropic.Anthropic()
69+
messages = [{"role": "user", "content": QUESTION}]
70+
response = ask_model(client, messages, TOOLS)
71+
for block in response.content:
72+
if block.type == "tool_use":
73+
messages.append(
74+
{"role": "assistant", "content": response.content},
75+
)
76+
messages.append(tool_result(block, run_tool(block)))
77+
final = ask_model(client, messages, TOOLS)
78+
print(final.content[0].text)
79+
80+
81+
if __name__ == "__main__":
82+
main()

‎mcp-vs-api-calls/server.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from mcp.server import MCPServer
2+
from tool import get_package_info
3+
4+
mcp = MCPServer("pypi-tools")
5+
mcp.add_tool(get_package_info)
6+
7+
if __name__ == "__main__":
8+
mcp.run()

‎mcp-vs-api-calls/tool.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import json
2+
import urllib.request
3+
4+
5+
def get_package_info(package_name: str) -> dict:
6+
"""
7+
Look up a package on PyPI
8+
and return its latest version and summary.
9+
"""
10+
url = f"https://pypi.org/pypi/{package_name}/json"
11+
with urllib.request.urlopen(url) as response:
12+
data = json.load(response)
13+
info = data["info"]
14+
return {
15+
"name": info["name"],
16+
"version": info["version"],
17+
"summary": info["summary"],
18+
}

0 commit comments

Comments
 (0)