pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/modelcontextprotocol/python-sdk/commit/c7671e470c235971b63c41be183537074ff8fa91

ssorigen="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/global-b40ec823a1a6a1af.css" /> Add pyright strict mode on the whole project (#1254) · modelcontextprotocol/python-sdk@c7671e4 · GitHub
Skip to content

Commit c7671e4

Browse files
authored
Add pyright strict mode on the whole project (#1254)
1 parent ef4e167 commit c7671e4

75 files changed

Lines changed: 652 additions & 674 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/shared.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ on:
66
permissions:
77
contents: read
88

9+
env:
10+
COLUMNS: 150
11+
912
jobs:
1013
pre-commit:
1114
runs-on: ubuntu-latest

README.md

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ from contextlib import asynccontextmanager
197197
from dataclasses import dataclass
198198

199199
from mcp.server.fastmcp import Context, FastMCP
200+
from mcp.server.session import ServerSession
200201

201202

202203
# Mock database class for example
@@ -242,7 +243,7 @@ mcp = FastMCP("My App", lifespan=app_lifespan)
242243

243244
# Access type-safe lifespan context in tools
244245
@mcp.tool()
245-
def query_db(ctx: Context) -> str:
246+
def query_db(ctx: Context[ServerSession, AppContext]) -> str:
246247
"""Tool that uses initialized resources."""
247248
db = ctx.request_context.lifespan_context.db
248249
return db.query()
@@ -314,12 +315,13 @@ Tools can optionally receive a Context object by including a parameter with the
314315
<!-- snippet-source examples/snippets/servers/tool_progress.py -->
315316
```python
316317
from mcp.server.fastmcp import Context, FastMCP
318+
from mcp.server.session import ServerSession
317319

318320
mcp = FastMCP(name="Progress Example")
319321

320322

321323
@mcp.tool()
322-
async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str:
324+
async def long_running_task(task_name: str, ctx: Context[ServerSession, None], steps: int = 5) -> str:
323325
"""Execute a task with progress updates."""
324326
await ctx.info(f"Starting: {task_name}")
325327

@@ -445,7 +447,7 @@ def get_user(user_id: str) -> UserProfile:
445447

446448
# Classes WITHOUT type hints cannot be used for structured output
447449
class UntypedConfig:
448-
def __init__(self, setting1, setting2):
450+
def __init__(self, setting1, setting2): # type: ignore[reportMissingParameterType]
449451
self.setting1 = setting1
450452
self.setting2 = setting2
451453

@@ -571,12 +573,13 @@ The Context object provides the following capabilities:
571573
<!-- snippet-source examples/snippets/servers/tool_progress.py -->
572574
```python
573575
from mcp.server.fastmcp import Context, FastMCP
576+
from mcp.server.session import ServerSession
574577

575578
mcp = FastMCP(name="Progress Example")
576579

577580

578581
@mcp.tool()
579-
async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str:
582+
async def long_running_task(task_name: str, ctx: Context[ServerSession, None], steps: int = 5) -> str:
580583
"""Execute a task with progress updates."""
581584
await ctx.info(f"Starting: {task_name}")
582585

@@ -694,6 +697,7 @@ Request additional information from users. This example shows an Elicitation dur
694697
from pydantic import BaseModel, Field
695698

696699
from mcp.server.fastmcp import Context, FastMCP
700+
from mcp.server.session import ServerSession
697701

698702
mcp = FastMCP(name="Elicitation Example")
699703

@@ -709,12 +713,7 @@ class BookingPreferences(BaseModel):
709713

710714

711715
@mcp.tool()
712-
async def book_table(
713-
date: str,
714-
time: str,
715-
party_size: int,
716-
ctx: Context,
717-
) -> str:
716+
async def book_table(date: str, time: str, party_size: int, ctx: Context[ServerSession, None]) -> str:
718717
"""Book a table with date availability check."""
719718
# Check if date is available
720719
if date == "2024-12-25":
@@ -750,13 +749,14 @@ Tools can interact with LLMs through sampling (generating text):
750749
<!-- snippet-source examples/snippets/servers/sampling.py -->
751750
```python
752751
from mcp.server.fastmcp import Context, FastMCP
752+
from mcp.server.session import ServerSession
753753
from mcp.types import SamplingMessage, TextContent
754754

755755
mcp = FastMCP(name="Sampling Example")
756756

757757

758758
@mcp.tool()
759-
async def generate_poem(topic: str, ctx: Context) -> str:
759+
async def generate_poem(topic: str, ctx: Context[ServerSession, None]) -> str:
760760
"""Generate a poem using LLM sampling."""
761761
prompt = f"Write a short poem about {topic}"
762762

@@ -785,12 +785,13 @@ Tools can send logs and notifications through the context:
785785
<!-- snippet-source examples/snippets/servers/notifications.py -->
786786
```python
787787
from mcp.server.fastmcp import Context, FastMCP
788+
from mcp.server.session import ServerSession
788789

789790
mcp = FastMCP(name="Notifications Example")
790791

791792

792793
@mcp.tool()
793-
async def process_data(data: str, ctx: Context) -> str:
794+
async def process_data(data: str, ctx: Context[ServerSession, None]) -> str:
794795
"""Process data with logging."""
795796
# Different log levels
796797
await ctx.debug(f"Debug: Processing '{data}'")
@@ -1244,6 +1245,7 @@ Run from the repository root:
12441245

12451246
from collections.abc import AsyncIterator
12461247
from contextlib import asynccontextmanager
1248+
from typing import Any
12471249

12481250
import mcp.server.stdio
12491251
import mcp.types as types
@@ -1272,7 +1274,7 @@ class Database:
12721274

12731275

12741276
@asynccontextmanager
1275-
async def server_lifespan(_server: Server) -> AsyncIterator[dict]:
1277+
async def server_lifespan(_server: Server) -> AsyncIterator[dict[str, Any]]:
12761278
"""Manage server startup and shutdown lifecycle."""
12771279
# Initialize resources on startup
12781280
db = await Database.connect()
@@ -1304,7 +1306,7 @@ async def handle_list_tools() -> list[types.Tool]:
13041306

13051307

13061308
@server.call_tool()
1307-
async def query_db(name: str, arguments: dict) -> list[types.TextContent]:
1309+
async def query_db(name: str, arguments: dict[str, Any]) -> list[types.TextContent]:
13081310
"""Handle database query tool call."""
13091311
if name != "query_db":
13101312
raise ValueError(f"Unknown tool: {name}")
@@ -1558,7 +1560,7 @@ server_params = StdioServerParameters(
15581560

15591561
# Optional: create a sampling callback
15601562
async def handle_sampling_message(
1561-
context: RequestContext, params: types.CreateMessageRequestParams
1563+
context: RequestContext[ClientSession, None], params: types.CreateMessageRequestParams
15621564
) -> types.CreateMessageResult:
15631565
print(f"Sampling request: {params.messages}")
15641566
return types.CreateMessageResult(

examples/clients/simple-auth-client/mcp_simple_auth_client/main.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,7 @@ async def _default_redirect_handler(authorization_url: str) -> None:
188188
# Create OAuth authentication handler using the new interface
189189
oauth_auth = OAuthClientProvider(
190190
server_url=self.server_url.replace("/mcp", ""),
191-
client_metadata=OAuthClientMetadata.model_validate(
192-
client_metadata_dict
193-
),
191+
client_metadata=OAuthClientMetadata.model_validate(client_metadata_dict),
194192
storage=InMemoryTokenStorage(),
195193
redirect_handler=_default_redirect_handler,
196194
callback_handler=callback_handler,
@@ -322,9 +320,7 @@ async def interactive_loop(self):
322320
await self.call_tool(tool_name, arguments)
323321

324322
else:
325-
print(
326-
"❌ Unknown command. Try 'list', 'call <tool_name>', or 'quit'"
327-
)
323+
print("❌ Unknown command. Try 'list', 'call <tool_name>', or 'quit'")
328324

329325
except KeyboardInterrupt:
330326
print("\n\n👋 Goodbye!")

examples/clients/simple-auth-client/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ select = ["E", "F", "I"]
3939
ignore = []
4040

4141
[tool.ruff]
42-
line-length = 88
42+
line-length = 120
4343
target-version = "py310"
4444

4545
[tool.uv]

examples/clients/simple-chatbot/mcp_simple_chatbot/main.py

Lines changed: 13 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212
from mcp.client.stdio import stdio_client
1313

1414
# Configure logging
15-
logging.basicConfig(
16-
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
17-
)
15+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
1816

1917

2018
class Configuration:
@@ -75,29 +73,19 @@ def __init__(self, name: str, config: dict[str, Any]) -> None:
7573

7674
async def initialize(self) -> None:
7775
"""Initialize the server connection."""
78-
command = (
79-
shutil.which("npx")
80-
if self.config["command"] == "npx"
81-
else self.config["command"]
82-
)
76+
command = shutil.which("npx") if self.config["command"] == "npx" else self.config["command"]
8377
if command is None:
8478
raise ValueError("The command must be a valid string and cannot be None.")
8579

8680
server_params = StdioServerParameters(
8781
command=command,
8882
args=self.config["args"],
89-
env={**os.environ, **self.config["env"]}
90-
if self.config.get("env")
91-
else None,
83+
env={**os.environ, **self.config["env"]} if self.config.get("env") else None,
9284
)
9385
try:
94-
stdio_transport = await self.exit_stack.enter_async_context(
95-
stdio_client(server_params)
96-
)
86+
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
9787
read, write = stdio_transport
98-
session = await self.exit_stack.enter_async_context(
99-
ClientSession(read, write)
100-
)
88+
session = await self.exit_stack.enter_async_context(ClientSession(read, write))
10189
await session.initialize()
10290
self.session = session
10391
except Exception as e:
@@ -122,10 +110,7 @@ async def list_tools(self) -> list[Any]:
122110

123111
for item in tools_response:
124112
if isinstance(item, tuple) and item[0] == "tools":
125-
tools.extend(
126-
Tool(tool.name, tool.description, tool.inputSchema, tool.title)
127-
for tool in item[1]
128-
)
113+
tools.extend(Tool(tool.name, tool.description, tool.inputSchema, tool.title) for tool in item[1])
129114

130115
return tools
131116

@@ -164,9 +149,7 @@ async def execute_tool(
164149

165150
except Exception as e:
166151
attempt += 1
167-
logging.warning(
168-
f"Error executing tool: {e}. Attempt {attempt} of {retries}."
169-
)
152+
logging.warning(f"Error executing tool: {e}. Attempt {attempt} of {retries}.")
170153
if attempt < retries:
171154
logging.info(f"Retrying in {delay} seconds...")
172155
await asyncio.sleep(delay)
@@ -209,9 +192,7 @@ def format_for_llm(self) -> str:
209192
args_desc = []
210193
if "properties" in self.input_schema:
211194
for param_name, param_info in self.input_schema["properties"].items():
212-
arg_desc = (
213-
f"- {param_name}: {param_info.get('description', 'No description')}"
214-
)
195+
arg_desc = f"- {param_name}: {param_info.get('description', 'No description')}"
215196
if param_name in self.input_schema.get("required", []):
216197
arg_desc += " (required)"
217198
args_desc.append(arg_desc)
@@ -281,10 +262,7 @@ def get_response(self, messages: list[dict[str, str]]) -> str:
281262
logging.error(f"Status code: {status_code}")
282263
logging.error(f"Response details: {e.response.text}")
283264

284-
return (
285-
f"I encountered an error: {error_message}. "
286-
"Please try again or rephrase your request."
287-
)
265+
return f"I encountered an error: {error_message}. Please try again or rephrase your request."
288266

289267

290268
class ChatSession:
@@ -323,17 +301,13 @@ async def process_llm_response(self, llm_response: str) -> str:
323301
tools = await server.list_tools()
324302
if any(tool.name == tool_call["tool"] for tool in tools):
325303
try:
326-
result = await server.execute_tool(
327-
tool_call["tool"], tool_call["arguments"]
328-
)
304+
result = await server.execute_tool(tool_call["tool"], tool_call["arguments"])
329305

330306
if isinstance(result, dict) and "progress" in result:
331307
progress = result["progress"]
332308
total = result["total"]
333309
percentage = (progress / total) * 100
334-
logging.info(
335-
f"Progress: {progress}/{total} ({percentage:.1f}%)"
336-
)
310+
logging.info(f"Progress: {progress}/{total} ({percentage:.1f}%)")
337311

338312
return f"Tool execution result: {result}"
339313
except Exception as e:
@@ -408,9 +382,7 @@ async def start(self) -> None:
408382

409383
final_response = self.llm_client.get_response(messages)
410384
logging.info("\nFinal response: %s", final_response)
411-
messages.append(
412-
{"role": "assistant", "content": final_response}
413-
)
385+
messages.append({"role": "assistant", "content": final_response})
414386
else:
415387
messages.append({"role": "assistant", "content": llm_response})
416388

@@ -426,10 +398,7 @@ async def main() -> None:
426398
"""Initialize and run the chat session."""
427399
config = Configuration()
428400
server_config = config.load_config("servers_config.json")
429-
servers = [
430-
Server(name, srv_config)
431-
for name, srv_config in server_config["mcpServers"].items()
432-
]
401+
servers = [Server(name, srv_config) for name, srv_config in server_config["mcpServers"].items()]
433402
llm_client = LLMClient(config.llm_api_key)
434403
chat_session = ChatSession(servers, llm_client)
435404
await chat_session.start()

examples/clients/simple-chatbot/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ select = ["E", "F", "I"]
4141
ignore = []
4242

4343
[tool.ruff]
44-
line-length = 88
44+
line-length = 120
4545
target-version = "py310"
4646

4747
[tool.uv]

examples/servers/simple-auth/mcp_simple_auth/server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ class ResourceServerSettings(BaseSettings):
4545
# RFC 8707 resource validation
4646
oauth_strict: bool = False
4747

48-
def __init__(self, **data):
48+
# TODO(Marcelo): Is this even needed? I didn't have time to check.
49+
def __init__(self, **data: Any):
4950
"""Initialize settings with values from environment variables."""
5051
super().__init__(**data)
5152

examples/servers/simple-auth/mcp_simple_auth/simple_auth_provider.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class SimpleAuthSettings(BaseSettings):
4646
mcp_scope: str = "user"
4747

4848

49-
class SimpleOAuthProvider(OAuthAuthorizationServerProvider):
49+
class SimpleOAuthProvider(OAuthAuthorizationServerProvider[AuthorizationCode, RefreshToken, AccessToken]):
5050
"""
5151
Simple OAuth provider for demo purposes.
5252
@@ -116,7 +116,7 @@ async def get_login_page(self, state: str) -> HTMLResponse:
116116
<p>This is a simplified authentication demo. Use the demo credentials below:</p>
117117
<p><strong>Username:</strong> demo_user<br>
118118
<strong>Password:</strong> demo_password</p>
119-
119+
120120
<form action="{self.server_url.rstrip("/")}/login/callback" method="post">
121121
<input type="hidden" name="state" value="{state}">
122122
<div class="form-group">
@@ -264,7 +264,8 @@ async def exchange_refresh_token(
264264
"""Exchange refresh token - not supported in this example."""
265265
raise NotImplementedError("Refresh tokens not supported")
266266

267-
async def revoke_token(self, token: str, token_type_hint: str | None = None) -> None:
267+
# TODO(Marcelo): The type hint is wrong. We need to fix, and test to check if it works.
268+
async def revoke_token(self, token: str, token_type_hint: str | None = None) -> None: # type: ignore
268269
"""Revoke a token."""
269270
if token in self.tokens:
270271
del self.tokens[token]

examples/servers/simple-auth/mcp_simple_auth/token_verifier.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Example token verifier implementation using OAuth 2.0 Token Introspection (RFC 7662)."""
22

33
import logging
4+
from typing import Any
45

56
from mcp.server.auth.provider import AccessToken, TokenVerifier
67
from mcp.shared.auth_utils import check_resource_allowed, resource_url_from_server_url
@@ -79,13 +80,13 @@ async def verify_token(self, token: str) -> AccessToken | None:
7980
logger.warning(f"Token introspection failed: {e}")
8081
return None
8182

82-
def _validate_resource(self, token_data: dict) -> bool:
83+
def _validate_resource(self, token_data: dict[str, Any]) -> bool:
8384
"""Validate token was issued for this resource server."""
8485
if not self.server_url or not self.resource_url:
8586
return False # Fail if strict validation requested but URLs missing
8687

8788
# Check 'aud' claim first (standard JWT audience)
88-
aud = token_data.get("aud")
89+
aud: list[str] | str | None = token_data.get("aud")
8990
if isinstance(aud, list):
9091
for audience in aud:
9192
if self._is_valid_resource(audience):

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy