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