Skip to content

Commit 2c684fd

Browse files
authored
Standardize MCP interface for cross-language consistency
Aligns this implementation with the canonical MCP interface specification.
1 parent a212728 commit 2c684fd

File tree

5 files changed

+94
-66
lines changed

5 files changed

+94
-66
lines changed

mcp_starter/prompts.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,31 @@
22

33
from __future__ import annotations
44

5+
from typing import Annotated
6+
57
from mcp.server.fastmcp import FastMCP
8+
from pydantic import Field
69

710

811
def register_prompts(mcp: FastMCP) -> None:
9-
"""Register all prompts with the MCP server."""
12+
"""Register all prompts with the MCP server.
13+
14+
Note: The Python MCP SDK's PromptArgument model does not support a 'title' field,
15+
only 'name', 'description', and 'required'. This is a limitation of the SDK compared
16+
to the canonical MCP interface. The 'description' field is used for both purposes.
17+
"""
1018

1119
@mcp.prompt(
1220
title="Greeting Prompt",
13-
description="Generate a greeting in a specific style",
21+
description="Generate a greeting message",
1422
)
15-
def greet(name: str, style: str = "casual") -> str:
23+
def greet(
24+
name: Annotated[str, Field(title="Name", description="Name of the person to greet")],
25+
style: Annotated[
26+
str,
27+
Field(title="Style", description="Greeting style (formal/casual)"),
28+
] = "casual",
29+
) -> str:
1630
"""Generate a greeting prompt.
1731
1832
Args:
@@ -28,9 +42,22 @@ def greet(name: str, style: str = "casual") -> str:
2842

2943
@mcp.prompt(
3044
title="Code Review",
31-
description="Request a code review with specific focus areas",
45+
description="Review code for potential improvements",
3246
)
33-
def code_review(code: str, language: str, focus: str = "all") -> str:
47+
def code_review(
48+
code: Annotated[str, Field(title="Code", description="The code to review")],
49+
language: Annotated[
50+
str,
51+
Field(title="Language", description="Programming language of the code"),
52+
] = "python",
53+
focus: Annotated[
54+
str,
55+
Field(
56+
title="Focus",
57+
description="What to focus on (security, performance, readability, or all)",
58+
),
59+
] = "all",
60+
) -> str:
3461
"""Generate a code review prompt.
3562
3663
Args:

mcp_starter/resources.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
def register_resources(mcp: FastMCP) -> None:
1818
"""Register all resources and templates with the MCP server."""
1919

20-
@mcp.resource("info://about")
20+
@mcp.resource("about://server", name="About", description="Information about this MCP server")
2121
def about_resource() -> str:
2222
"""Information about this MCP server."""
2323
return """MCP Python Starter v1.0.0
@@ -31,7 +31,9 @@ def about_resource() -> str:
3131
3232
For more information, visit: https://modelcontextprotocol.io"""
3333

34-
@mcp.resource("file://example.md")
34+
@mcp.resource(
35+
"doc://example", name="Example Document", description="An example document resource"
36+
)
3537
def example_file() -> str:
3638
"""An example markdown document."""
3739
return """# Example Document
@@ -54,7 +56,11 @@ def example_file() -> str:
5456
- [Python SDK](https://github.com/modelcontextprotocol/python-sdk)
5557
"""
5658

57-
@mcp.resource("greeting://{name}")
59+
@mcp.resource(
60+
"greeting://{name}",
61+
name="Personalized Greeting",
62+
description="A personalized greeting for a specific person",
63+
)
5864
def greeting_template(name: str) -> str:
5965
"""Generate a personalized greeting.
6066
@@ -63,14 +69,18 @@ def greeting_template(name: str) -> str:
6369
"""
6470
return f"Hello, {name}! This greeting was generated just for you."
6571

66-
@mcp.resource("data://items/{item_id}")
67-
def item_data(item_id: str) -> str:
72+
@mcp.resource(
73+
"item://{id}",
74+
name="Item Data",
75+
description="Data for a specific item by ID",
76+
)
77+
def item_data(id: str) -> str:
6878
"""Get data for a specific item by ID.
6979
7080
Args:
71-
item_id: The item ID to look up
81+
id: The item ID to look up
7282
"""
73-
item = ITEMS_DATA.get(item_id)
83+
item = ITEMS_DATA.get(id)
7484
if not item:
75-
raise ValueError(f"Item not found: {item_id}")
76-
return json.dumps({"id": item_id, **item}, indent=2)
85+
raise ValueError(f"Item not found: {id}")
86+
return json.dumps({"id": id, **item}, indent=2)

mcp_starter/server.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@
5151
5252
## Available Resources
5353
54-
- **info://about**: Server information
55-
- **file://example.md**: Sample markdown document
54+
- **about://server**: Server information
55+
- **doc://example**: Sample markdown document
5656
- **greeting://{name}**: Personalized greeting template
57-
- **data://items/{item_id}**: Item data by ID
57+
- **item://{id}**: Item data by ID
5858
5959
## Available Prompts
6060

mcp_starter/tools.py

Lines changed: 39 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@
2626

2727
import asyncio
2828
import random
29-
from typing import Any, Literal
29+
from typing import Annotated, Any, Literal
3030

3131
from mcp.server.fastmcp import Context, FastMCP
3232
from mcp.server.session import ServerSession
3333
from mcp.types import Icon, ToolAnnotations
34+
from pydantic import Field
3435

3536
from .icons import (
3637
ABACUS_ICON,
@@ -67,12 +68,10 @@ def register_tools(mcp: FastMCP) -> None:
6768
),
6869
],
6970
)
70-
def hello(name: str) -> str:
71-
"""A friendly greeting tool that says hello to someone.
72-
73-
Args:
74-
name: The name to greet
75-
"""
71+
def hello(
72+
name: Annotated[str, Field(title="Name", description="Name of the person to greet")],
73+
) -> str:
74+
"""Say hello to a person"""
7675
return f"Hello, {name}! Welcome to MCP."
7776

7877
@mcp.tool(
@@ -91,15 +90,13 @@ def hello(name: str) -> str:
9190
),
9291
],
9392
)
94-
def get_weather(location: str) -> dict[str, Any]:
95-
"""Get current weather for a location (simulated).
96-
97-
Args:
98-
location: City name or coordinates
99-
"""
93+
def get_weather(
94+
city: Annotated[str, Field(title="City", description="City name to get weather for")],
95+
) -> dict[str, Any]:
96+
"""Get the current weather for a city"""
10097
conditions = ["sunny", "cloudy", "rainy", "windy"]
10198
return {
102-
"location": location,
99+
"location": city,
103100
"temperature": round(15 + random.random() * 20),
104101
"unit": "celsius",
105102
"conditions": random.choice(conditions),
@@ -123,16 +120,15 @@ def get_weather(location: str) -> dict[str, Any]:
123120
],
124121
)
125122
async def ask_llm(
126-
prompt: str,
123+
prompt: Annotated[
124+
str, Field(title="Prompt", description="The question or prompt to send to the LLM")
125+
],
127126
ctx: Context[ServerSession, None],
128-
max_tokens: int = 100,
127+
maxTokens: Annotated[
128+
int, Field(title="Max Tokens", description="Maximum tokens in response")
129+
] = 100,
129130
) -> str:
130-
"""Ask the connected LLM a question using sampling.
131-
132-
Args:
133-
prompt: The question or prompt for the LLM
134-
max_tokens: Maximum tokens in response
135-
"""
131+
"""Ask the connected LLM a question using sampling"""
136132
try:
137133
result = await ctx.session.create_message(
138134
messages=[
@@ -141,7 +137,7 @@ async def ask_llm(
141137
"content": {"type": "text", "text": prompt},
142138
}
143139
],
144-
max_tokens=max_tokens,
140+
max_tokens=maxTokens,
145141
)
146142
if result.content.type == "text":
147143
return f"LLM Response: {result.content.text}"
@@ -166,17 +162,12 @@ async def ask_llm(
166162
],
167163
)
168164
async def long_task(
169-
task_name: str,
165+
taskName: Annotated[str, Field(title="Task Name", description="Name for this task")],
170166
ctx: Context[ServerSession, None],
167+
steps: Annotated[int, Field(title="Steps", description="Number of steps to simulate")] = 5,
171168
) -> str:
172-
"""A task that takes 5 seconds and reports progress along the way.
173-
174-
Args:
175-
task_name: Name for this task
176-
"""
177-
steps = 5
178-
179-
await ctx.info(f"Starting task: {task_name}")
169+
"""Simulate a long-running task with progress updates"""
170+
await ctx.info(f"Starting task: {taskName}")
180171

181172
for i in range(steps):
182173
await ctx.report_progress(
@@ -188,7 +179,7 @@ async def long_task(
188179

189180
await ctx.report_progress(progress=1.0, total=1.0, message="Complete!")
190181

191-
return f'Task "{task_name}" completed successfully after {steps} steps!'
182+
return f'Task "{taskName}" completed successfully after {steps} steps!'
192183

193184
@mcp.tool(
194185
annotations=ToolAnnotations(
@@ -207,7 +198,7 @@ async def long_task(
207198
],
208199
)
209200
async def load_bonus_tool(ctx: Context[ServerSession, None]) -> str:
210-
"""Dynamically loads a bonus tool that wasn't available at startup."""
201+
"""Dynamically register a new bonus tool"""
211202
global _bonus_tool_loaded
212203

213204
if _bonus_tool_loaded:
@@ -284,14 +275,16 @@ def bonus_calculator(a: float, b: float, operation: Operation) -> str:
284275
),
285276
)
286277
async def confirm_action(
287-
action: str,
278+
action: Annotated[
279+
str, Field(title="Action", description="Description of the action to confirm")
280+
],
288281
ctx: Context[ServerSession, None],
282+
destructive: Annotated[
283+
bool,
284+
Field(title="Destructive", description="Whether the action is destructive"),
285+
] = False,
289286
) -> str:
290-
"""Demonstrates elicitation - requests user confirmation before proceeding.
291-
292-
Args:
293-
action: The action to confirm with the user
294-
"""
287+
"""Request user confirmation before proceeding"""
295288
try:
296289
# Form elicitation: Display a structured form with typed fields
297290
# The client renders this as a dialog/form based on the JSON schema
@@ -338,17 +331,15 @@ async def confirm_action(
338331
),
339332
)
340333
async def get_feedback(
334+
question: Annotated[
335+
str, Field(title="Question", description="The question to ask the user")
336+
],
341337
ctx: Context[ServerSession, None],
342-
topic: str = "",
343338
) -> str:
344-
"""Demonstrates URL elicitation - opens a feedback form in the browser.
345-
346-
Args:
347-
topic: Optional topic for the feedback
348-
"""
339+
"""Request feedback from the user"""
349340
feedback_url = "https://github.com/SamMorrowDrums/mcp-starters/issues/new?template=workshop-feedback.yml"
350-
if topic:
351-
feedback_url += f"&title={topic}"
341+
if question:
342+
feedback_url += f"&title={question}"
352343

353344
try:
354345
# URL elicitation: Open a web page in the user's browser

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ target-version = "py311"
4040

4141
[tool.ruff.lint]
4242
select = ["E", "F", "I", "N", "W", "UP", "B", "SIM", "RUF"]
43-
ignore = ["E501"]
43+
ignore = ["E501", "N803"] # N803: Allow camelCase for MCP cross-language consistency
4444

4545
[tool.ruff.format]
4646
quote-style = "double"

0 commit comments

Comments
 (0)