#
tokens: 2896/50000 5/5 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .python-version
├── pyproject.toml
├── README.md
├── src
│   └── mcp_server_on_raspi
│       ├── __init__.py
│       └── server.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
1 | 3.13
2 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # mcp-server-on-raspi 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-server-on-raspi": {
 45 |       "command": "uv",
 46 |       "args": [
 47 |         "--directory",
 48 |         "/Users/daikiwatanabe/ghq/github.com/daikw/mcp-server-on-raspi",
 49 |         "run",
 50 |         "mcp-server-on-raspi"
 51 |       ]
 52 |     }
 53 |   }
 54 |   ```
 55 | </details>
 56 | 
 57 | <details>
 58 |   <summary>Published Servers Configuration</summary>
 59 |   ```
 60 |   "mcpServers": {
 61 |     "mcp-server-on-raspi": {
 62 |       "command": "uvx",
 63 |       "args": [
 64 |         "mcp-server-on-raspi"
 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/daikiwatanabe/ghq/github.com/daikw/mcp-server-on-raspi run mcp-server-on-raspi
108 | ```
109 | 
110 | 
111 | Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.
```

--------------------------------------------------------------------------------
/src/mcp_server_on_raspi/__init__.py:
--------------------------------------------------------------------------------

```python
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']
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "mcp-server-on-raspi"
 3 | version = "0.1.0"
 4 | description = "A MCP server project"
 5 | readme = "README.md"
 6 | requires-python = ">=3.13"
 7 | dependencies = [ "mcp>=1.1.0",]
 8 | [[project.authors]]
 9 | name = "daikw"
10 | email = "[email protected]"
11 | 
12 | [build-system]
13 | requires = [ "hatchling",]
14 | build-backend = "hatchling.build"
15 | 
16 | [project.scripts]
17 | mcp-server-on-raspi = "mcp_server_on_raspi:main"
18 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_on_raspi/server.py:
--------------------------------------------------------------------------------

```python
  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-server-on-raspi")
 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-server-on-raspi",
158 |                 server_version="0.1.0",
159 |                 capabilities=server.get_capabilities(
160 |                     notification_options=NotificationOptions(),
161 |                     experimental_capabilities={},
162 |                 ),
163 |             ),
164 |         )
```