Skip to content
This repository was archived by the owner on Oct 27, 2025. It is now read-only.

Commit 9c35ed8

Browse files
committed
first linkup mcp server
0 parents  commit 9c35ed8

File tree

7 files changed

+561
-0
lines changed

7 files changed

+561
-0
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Python-generated files
2+
__pycache__/
3+
*.py[oc]
4+
build/
5+
dist/
6+
wheels/
7+
*.egg-info
8+
9+
# Virtual environments
10+
.venv

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.11

README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# mcp-search-linkup MCP server
2+
3+
A MCP server to search web with Linkup
4+
5+
## Components
6+
7+
### Resources
8+
9+
The server implements a simple note storage system with:
10+
- Custom note:// URI scheme for accessing individual notes
11+
- Each note resource has a name, description and text/plain mimetype
12+
13+
### Prompts
14+
15+
The server provides a single prompt:
16+
- summarize-notes: Creates summaries of all stored notes
17+
- Optional "style" argument to control detail level (brief/detailed)
18+
- Generates prompt combining all current notes with style preference
19+
20+
### Tools
21+
22+
The server implements one tool:
23+
- add-note: Adds a new note to the server
24+
- Takes "name" and "content" as required string arguments
25+
- Updates server state and notifies clients of resource changes
26+
27+
## Configuration
28+
29+
[TODO: Add configuration details specific to your implementation]
30+
31+
## Quickstart
32+
33+
### Install
34+
35+
#### Claude Desktop
36+
37+
On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
38+
On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
39+
40+
<details>
41+
<summary>Development/Unpublished Servers Configuration</summary>
42+
```
43+
"mcpServers": {
44+
"mcp-search-linkup": {
45+
"command": "uv",
46+
"args": [
47+
"--directory",
48+
"/Users/thot/Desktop/linkup/mcp-search-linkup",
49+
"run",
50+
"mcp-search-linkup"
51+
]
52+
}
53+
}
54+
```
55+
</details>
56+
57+
<details>
58+
<summary>Published Servers Configuration</summary>
59+
```
60+
"mcpServers": {
61+
"mcp-search-linkup": {
62+
"command": "uvx",
63+
"args": [
64+
"mcp-search-linkup"
65+
]
66+
}
67+
}
68+
```
69+
</details>
70+
71+
## Development
72+
73+
### Building and Publishing
74+
75+
To prepare the package for distribution:
76+
77+
1. Sync dependencies and update lockfile:
78+
```bash
79+
uv sync
80+
```
81+
82+
2. Build package distributions:
83+
```bash
84+
uv build
85+
```
86+
87+
This will create source and wheel distributions in the `dist/` directory.
88+
89+
3. Publish to PyPI:
90+
```bash
91+
uv publish
92+
```
93+
94+
Note: You'll need to set PyPI credentials via environment variables or command flags:
95+
- Token: `--token` or `UV_PUBLISH_TOKEN`
96+
- Or username/password: `--username`/`UV_PUBLISH_USERNAME` and `--password`/`UV_PUBLISH_PASSWORD`
97+
98+
### Debugging
99+
100+
Since MCP servers run over stdio, debugging can be challenging. For the best debugging
101+
experience, we strongly recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector).
102+
103+
104+
You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command:
105+
106+
```bash
107+
npx @modelcontextprotocol/inspector uv --directory /Users/thot/Desktop/linkup/mcp-search-linkup run mcp-search-linkup
108+
```
109+
110+
111+
Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.

pyproject.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[project]
2+
name = "mcp-search-linkup"
3+
version = "0.1.0"
4+
description = "A MCP server to search web with Linkup"
5+
readme = "README.md"
6+
requires-python = ">=3.11"
7+
dependencies = [
8+
"linkup-sdk>=0.1.8",
9+
"mcp>=1.0.0",
10+
]
11+
[[project.authors]]
12+
name = "Guillaume Larcher"
13+
14+
15+
[build-system]
16+
requires = [ "hatchling",]
17+
build-backend = "hatchling.build"
18+
19+
[project.scripts]
20+
mcp-search-linkup = "mcp_search_linkup:main"

src/mcp_search_linkup/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from . import server
2+
import asyncio
3+
4+
def main():
5+
"""Main entry point for the package."""
6+
asyncio.run(server.main())
7+
8+
# Optionally expose other important items at package level
9+
__all__ = ['main', 'server']

src/mcp_search_linkup/server.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import asyncio
2+
from linkup import LinkupClient
3+
from mcp.server.models import InitializationOptions
4+
import mcp.types as types
5+
from mcp.server import NotificationOptions, Server
6+
import mcp.server.stdio
7+
from pydantic import AnyUrl
8+
import logging
9+
10+
import os
11+
# Initialize the LinkupClient
12+
13+
client = LinkupClient()
14+
15+
server = Server("mcp-search-linkup")
16+
logger = logging.getLogger("mcp-search-linkup")
17+
logger.setLevel(logging.INFO)
18+
19+
## Logging
20+
@server.set_logging_level()
21+
async def set_logging_level(level: types.LoggingLevel) -> types.EmptyResult:
22+
logger.setLevel(level.upper())
23+
await server.request_context.session.send_log_message(
24+
level="info",
25+
data=f"Log level set to {level}",
26+
logger="mcp-search-linkup"
27+
)
28+
return types.EmptyResult()
29+
30+
## Resources
31+
@server.list_resources()
32+
async def list_resources() -> list[types.Resource]:
33+
return [
34+
types.Resource(
35+
uri=AnyUrl("https://www.thebridgechronicle.com/media"),
36+
name="The Bridge Chronicle",
37+
mimeType="text/html"
38+
)
39+
]
40+
41+
@server.read_resource()
42+
async def read_resource(uri: AnyUrl) -> str:
43+
if str(uri) == "https://www.thebridgechronicle.com/media":
44+
page = client.content(url="https://www.thebridgechronicle.com/news/capgemini-employees-walk-together-in-celebration-of-indias-independence")
45+
return page.content
46+
47+
48+
raise ValueError("Resource not found")
49+
50+
## Tools
51+
@server.list_tools()
52+
async def handle_list_tools() -> list[types.Tool]:
53+
"""
54+
List available search tools.
55+
"""
56+
return [
57+
types.Tool(
58+
name="search-web",
59+
description="Perform a web search query using Linkup. This tool is helpful for finding information on the web.",
60+
inputSchema={
61+
"type": "object",
62+
"properties": {
63+
"query": {"type": "string"},
64+
"depth": {
65+
"type": "string",
66+
"enum": ["standard", "deep"],
67+
"default": "standard"
68+
},
69+
"output_type": {
70+
"type": "string",
71+
"enum": ["searchResults", "sourcedAnswer", "structured"],
72+
"default": "sourcedAnswer"
73+
}
74+
},
75+
"required": ["query"],
76+
},
77+
)
78+
]
79+
80+
@server.call_tool()
81+
async def handle_call_tool(
82+
name: str, arguments: dict | None
83+
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
84+
"""
85+
Handle search tool execution requests.
86+
"""
87+
if name != "search-web":
88+
raise ValueError(f"Unknown tool: {name}")
89+
90+
if not arguments:
91+
raise ValueError("Missing arguments")
92+
93+
query = arguments.get("query")
94+
depth = arguments.get("depth", "standard")
95+
output_type = arguments.get("output_type", "sourcedAnswer")
96+
97+
if not query:
98+
raise ValueError("Missing query")
99+
100+
# Perform the search using LinkupClient
101+
search_response = client.search(
102+
query=query,
103+
depth=depth,
104+
output_type=output_type,
105+
)
106+
107+
return [
108+
types.TextContent(
109+
type="text",
110+
text=str(search_response),
111+
)
112+
]
113+
114+
async def main():
115+
# Run the server using stdin/stdout streams
116+
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
117+
await server.run(
118+
read_stream,
119+
write_stream,
120+
InitializationOptions(
121+
server_name="mcp-search-linkup",
122+
server_version="0.1.0",
123+
capabilities=server.get_capabilities(
124+
notification_options=NotificationOptions(),
125+
experimental_capabilities={},
126+
),
127+
),
128+
)
129+
130+
if __name__ == "__main__":
131+
asyncio.run(main())

0 commit comments

Comments
 (0)