Skip to content

Commit ab49a66

Browse files
authored
examples: add mcp server for web_search web_crawl (#585)
1 parent 16f344f commit ab49a66

File tree

3 files changed

+172
-12
lines changed

3 files changed

+172
-12
lines changed

examples/README.md

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,124 @@
11
# Running Examples
22

33
Run the examples in this directory with:
4+
45
```sh
56
# Run example
67
python3 examples/<example>.py
8+
9+
# or with uv
10+
uv run examples/<example>.py
711
```
812

913
See [ollama/docs/api.md](https://github.com/ollama/ollama/blob/main/docs/api.md) for full API documentation
1014

1115
### Chat - Chat with a model
16+
1217
- [chat.py](chat.py)
1318
- [async-chat.py](async-chat.py)
1419
- [chat-stream.py](chat-stream.py) - Streamed outputs
1520
- [chat-with-history.py](chat-with-history.py) - Chat with model and maintain history of the conversation
1621

17-
1822
### Generate - Generate text with a model
23+
1924
- [generate.py](generate.py)
2025
- [async-generate.py](async-generate.py)
2126
- [generate-stream.py](generate-stream.py) - Streamed outputs
2227
- [fill-in-middle.py](fill-in-middle.py) - Given a prefix and suffix, fill in the middle
2328

24-
2529
### Tools/Function Calling - Call a function with a model
30+
2631
- [tools.py](tools.py) - Simple example of Tools/Function Calling
2732
- [async-tools.py](async-tools.py)
2833
- [multi-tool.py](multi-tool.py) - Using multiple tools, with thinking enabled
2934

30-
#### gpt-oss
35+
#### gpt-oss
36+
3137
- [gpt-oss-tools.py](gpt-oss-tools.py)
32-
- [gpt-oss-tools-stream.py](gpt-oss-tools-stream.py)
38+
- [gpt-oss-tools-stream.py](gpt-oss-tools-stream.py)
3339
- [gpt-oss-tools-browser.py](gpt-oss-tools-browser.py) - Using browser research tools with gpt-oss
3440
- [gpt-oss-tools-browser-stream.py](gpt-oss-tools-browser-stream.py) - Using browser research tools with gpt-oss, with streaming enabled
3541

42+
### Web search
43+
44+
An API key from Ollama's cloud service is required. You can create one [here](https://ollama.com/settings/keys).
45+
46+
```shell
47+
export OLLAMA_API_KEY="your_api_key_here"
48+
```
49+
50+
- [web-search.py](web-search.py)
51+
52+
#### MCP server
53+
54+
The MCP server can be used with an MCP client like Cursor, Cline, Codex, Open WebUI, Goose, and more.
55+
56+
```sh
57+
uv run examples/web-search-mcp.py
58+
```
59+
60+
Configuration to use with an MCP client:
61+
62+
```json
63+
{
64+
"mcpServers": {
65+
"web_search": {
66+
"type": "stdio",
67+
"command": "uv",
68+
"args": ["run", "path/to/ollama-python/examples/web-search-mcp.py"],
69+
"env": { "OLLAMA_API_KEY": "your_api_key_here" }
70+
}
71+
}
72+
}
73+
```
74+
75+
- [web-search-mcp.py](web-search-mcp.py)
3676

3777
### Multimodal with Images - Chat with a multimodal (image chat) model
78+
3879
- [multimodal-chat.py](multimodal-chat.py)
3980
- [multimodal-generate.py](multimodal-generate.py)
4081

41-
4282
### Structured Outputs - Generate structured outputs with a model
83+
4384
- [structured-outputs.py](structured-outputs.py)
4485
- [async-structured-outputs.py](async-structured-outputs.py)
4586
- [structured-outputs-image.py](structured-outputs-image.py)
4687

47-
4888
### Ollama List - List all downloaded models and their properties
49-
- [list.py](list.py)
5089

90+
- [list.py](list.py)
5191

5292
### Ollama Show - Display model properties and capabilities
53-
- [show.py](show.py)
5493

94+
- [show.py](show.py)
5595

5696
### Ollama ps - Show model status with CPU/GPU usage
57-
- [ps.py](ps.py)
5897

98+
- [ps.py](ps.py)
5999

60100
### Ollama Pull - Pull a model from Ollama
101+
61102
Requirement: `pip install tqdm`
62-
- [pull.py](pull.py)
63103

104+
- [pull.py](pull.py)
64105

65106
### Ollama Create - Create a model from a Modelfile
66-
- [create.py](create.py)
67107

108+
- [create.py](create.py)
68109

69110
### Ollama Embed - Generate embeddings with a model
70-
- [embed.py](embed.py)
71111

112+
- [embed.py](embed.py)
72113

73114
### Thinking - Enable thinking mode for a model
115+
74116
- [thinking.py](thinking.py)
75117

76118
### Thinking (generate) - Enable thinking mode for a model
119+
77120
- [thinking-generate.py](thinking-generate.py)
78121

79122
### Thinking (levels) - Choose the thinking level
123+
80124
- [thinking-levels.py](thinking-levels.py)

examples/web-search-mcp.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# /// script
2+
# requires-python = ">=3.11"
3+
# dependencies = [
4+
# "mcp",
5+
# "rich",
6+
# "ollama",
7+
# ]
8+
# ///
9+
"""
10+
MCP stdio server exposing Ollama web_search and web_fetch as tools.
11+
12+
Environment:
13+
- OLLAMA_API_KEY (required): if set, will be used as Authorization header.
14+
"""
15+
16+
from __future__ import annotations
17+
18+
import asyncio
19+
from typing import Any, Dict
20+
21+
from ollama import Client
22+
23+
try:
24+
# Preferred high-level API (if available)
25+
from mcp.server.fastmcp import FastMCP # type: ignore
26+
27+
_FASTMCP_AVAILABLE = True
28+
except Exception:
29+
_FASTMCP_AVAILABLE = False
30+
31+
if not _FASTMCP_AVAILABLE:
32+
# Fallback to the low-level stdio server API
33+
from mcp.server import Server # type: ignore
34+
from mcp.server.stdio import stdio_server # type: ignore
35+
36+
37+
client = Client()
38+
39+
40+
def _web_search_impl(query: str, max_results: int = 3) -> Dict[str, Any]:
41+
res = client.web_search(query=query, max_results=max_results)
42+
return res.model_dump()
43+
44+
45+
def _web_fetch_impl(url: str) -> Dict[str, Any]:
46+
res = client.web_fetch(url=url)
47+
return res.model_dump()
48+
49+
50+
if _FASTMCP_AVAILABLE:
51+
app = FastMCP('ollama-search-fetch')
52+
53+
@app.tool()
54+
def web_search(query: str, max_results: int = 3) -> Dict[str, Any]:
55+
"""
56+
Perform a web search using Ollama's hosted search API.
57+
58+
Args:
59+
query: The search query to run.
60+
max_results: Maximum results to return (default: 3).
61+
62+
Returns:
63+
JSON-serializable dict matching ollama.WebSearchResponse.model_dump()
64+
"""
65+
66+
return _web_search_impl(query=query, max_results=max_results)
67+
68+
@app.tool()
69+
def web_fetch(url: str) -> Dict[str, Any]:
70+
"""
71+
Fetch the content of a web page for the provided URL.
72+
73+
Args:
74+
url: The absolute URL to fetch.
75+
76+
Returns:
77+
JSON-serializable dict matching ollama.WebFetchResponse.model_dump()
78+
"""
79+
80+
return _web_fetch_impl(url=url)
81+
82+
if __name__ == '__main__':
83+
app.run()
84+
85+
else:
86+
server = Server('ollama-search-fetch') # type: ignore[name-defined]
87+
88+
@server.tool() # type: ignore[attr-defined]
89+
async def web_search(query: str, max_results: int = 3) -> Dict[str, Any]:
90+
"""
91+
Perform a web search using Ollama's hosted search API.
92+
93+
Args:
94+
query: The search query to run.
95+
max_results: Maximum results to return (default: 3).
96+
"""
97+
98+
return await asyncio.to_thread(_web_search_impl, query, max_results)
99+
100+
@server.tool() # type: ignore[attr-defined]
101+
async def web_fetch(url: str) -> Dict[str, Any]:
102+
"""
103+
Fetch the content of a web page for the provided URL.
104+
105+
Args:
106+
url: The absolute URL to fetch.
107+
"""
108+
109+
return await asyncio.to_thread(_web_fetch_impl, url)
110+
111+
async def _main() -> None:
112+
async with stdio_server() as (read, write): # type: ignore[name-defined]
113+
await server.run(read, write) # type: ignore[attr-defined]
114+
115+
if __name__ == '__main__':
116+
asyncio.run(_main())
File renamed without changes.

0 commit comments

Comments
 (0)