Skip to content

Commit d1f49dd

Browse files
author
Polygon
committed
Initial commit
0 parents  commit d1f49dd

File tree

7 files changed

+619
-0
lines changed

7 files changed

+619
-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.13

README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# mcp-polygon MCP server
2+
3+
A MCP server project
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-polygon": {
45+
"command": "uv",
46+
"args": [
47+
"--directory",
48+
"/path/to/mcp-polygon",
49+
"run",
50+
"mcp-polygon"
51+
]
52+
}
53+
}
54+
```
55+
</details>
56+
57+
<details>
58+
<summary>Published Servers Configuration</summary>
59+
```
60+
"mcpServers": {
61+
"mcp-polygon": {
62+
"command": "uvx",
63+
"args": [
64+
"mcp-polygon"
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 /path/to/mcp-polygon run mcp-polygon
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-polygon"
3+
version = "0.1.0"
4+
description = "A MCP server project"
5+
readme = "README.md"
6+
requires-python = ">=3.13"
7+
dependencies = [
8+
"mcp>=1.4.1",
9+
"polygon-api-client>=1.14.4",
10+
]
11+
[[project.authors]]
12+
name = "Polygon"
13+
14+
15+
[build-system]
16+
requires = [ "hatchling",]
17+
build-backend = "hatchling.build"
18+
19+
[project.scripts]
20+
mcp-polygon = "mcp-polygon:main"

src/mcp-polygon/__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-polygon/server.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import asyncio
2+
3+
from mcp.server.models import InitializationOptions
4+
import mcp.types as types
5+
from mcp.server import NotificationOptions, Server
6+
from pydantic import AnyUrl
7+
import mcp.server.stdio
8+
9+
# Store notes as a simple key-value dict to demonstrate state management
10+
notes: dict[str, str] = {}
11+
12+
server = Server("mcp-polygon")
13+
14+
@server.list_resources()
15+
async def handle_list_resources() -> list[types.Resource]:
16+
"""
17+
List available note resources.
18+
Each note is exposed as a resource with a custom note:// URI scheme.
19+
"""
20+
return [
21+
types.Resource(
22+
uri=AnyUrl(f"note://internal/{name}"),
23+
name=f"Note: {name}",
24+
description=f"A simple note named {name}",
25+
mimeType="text/plain",
26+
)
27+
for name in notes
28+
]
29+
30+
@server.read_resource()
31+
async def handle_read_resource(uri: AnyUrl) -> str:
32+
"""
33+
Read a specific note's content by its URI.
34+
The note name is extracted from the URI host component.
35+
"""
36+
if uri.scheme != "note":
37+
raise ValueError(f"Unsupported URI scheme: {uri.scheme}")
38+
39+
name = uri.path
40+
if name is not None:
41+
name = name.lstrip("/")
42+
return notes[name]
43+
raise ValueError(f"Note not found: {name}")
44+
45+
@server.list_prompts()
46+
async def handle_list_prompts() -> list[types.Prompt]:
47+
"""
48+
List available prompts.
49+
Each prompt can have optional arguments to customize its behavior.
50+
"""
51+
return [
52+
types.Prompt(
53+
name="summarize-notes",
54+
description="Creates a summary of all notes",
55+
arguments=[
56+
types.PromptArgument(
57+
name="style",
58+
description="Style of the summary (brief/detailed)",
59+
required=False,
60+
)
61+
],
62+
)
63+
]
64+
65+
@server.get_prompt()
66+
async def handle_get_prompt(
67+
name: str, arguments: dict[str, str] | None
68+
) -> types.GetPromptResult:
69+
"""
70+
Generate a prompt by combining arguments with server state.
71+
The prompt includes all current notes and can be customized via arguments.
72+
"""
73+
if name != "summarize-notes":
74+
raise ValueError(f"Unknown prompt: {name}")
75+
76+
style = (arguments or {}).get("style", "brief")
77+
detail_prompt = " Give extensive details." if style == "detailed" else ""
78+
79+
return types.GetPromptResult(
80+
description="Summarize the current notes",
81+
messages=[
82+
types.PromptMessage(
83+
role="user",
84+
content=types.TextContent(
85+
type="text",
86+
text=f"Here are the current notes to summarize:{detail_prompt}\n\n"
87+
+ "\n".join(
88+
f"- {name}: {content}"
89+
for name, content in notes.items()
90+
),
91+
),
92+
)
93+
],
94+
)
95+
96+
@server.list_tools()
97+
async def handle_list_tools() -> list[types.Tool]:
98+
"""
99+
List available tools.
100+
Each tool specifies its arguments using JSON Schema validation.
101+
"""
102+
return [
103+
types.Tool(
104+
name="add-note",
105+
description="Add a new note",
106+
inputSchema={
107+
"type": "object",
108+
"properties": {
109+
"name": {"type": "string"},
110+
"content": {"type": "string"},
111+
},
112+
"required": ["name", "content"],
113+
},
114+
)
115+
]
116+
117+
@server.call_tool()
118+
async def handle_call_tool(
119+
name: str, arguments: dict | None
120+
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
121+
"""
122+
Handle tool execution requests.
123+
Tools can modify server state and notify clients of changes.
124+
"""
125+
if name != "add-note":
126+
raise ValueError(f"Unknown tool: {name}")
127+
128+
if not arguments:
129+
raise ValueError("Missing arguments")
130+
131+
note_name = arguments.get("name")
132+
content = arguments.get("content")
133+
134+
if not note_name or not content:
135+
raise ValueError("Missing name or content")
136+
137+
# Update server state
138+
notes[note_name] = content
139+
140+
# Notify clients that resources have changed
141+
await server.request_context.session.send_resource_list_changed()
142+
143+
return [
144+
types.TextContent(
145+
type="text",
146+
text=f"Added note '{note_name}' with content: {content}",
147+
)
148+
]
149+
150+
async def main():
151+
# Run the server using stdin/stdout streams
152+
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
153+
await server.run(
154+
read_stream,
155+
write_stream,
156+
InitializationOptions(
157+
server_name="mcp-polygon",
158+
server_version="0.1.0",
159+
capabilities=server.get_capabilities(
160+
notification_options=NotificationOptions(),
161+
experimental_capabilities={},
162+
),
163+
),
164+
)

0 commit comments

Comments
 (0)