Jeremiah Lowin commited on
Commit
22dfaa5
·
1 Parent(s): f59c3c3

add context object

Browse files
Files changed (2) hide show
  1. src/fastmcp/__init__.py +2 -2
  2. src/fastmcp/server.py +110 -2
src/fastmcp/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
  """FastMCP - A more ergonomic interface for MCP servers."""
2
 
3
- from .server import FastMCP
4
  from .utilities.types import Image
5
 
6
- __all__ = ["FastMCP", "Image"]
 
1
  """FastMCP - A more ergonomic interface for MCP servers."""
2
 
3
+ from .server import FastMCP, Context
4
  from .utilities.types import Image
5
 
6
+ __all__ = ["FastMCP", "Context", "Image"]
src/fastmcp/server.py CHANGED
@@ -1,9 +1,16 @@
1
  """FastMCP - A more ergonomic interface for MCP servers."""
2
 
 
 
 
 
 
 
 
3
  import asyncio
4
  import functools
5
  import json
6
- from typing import Any, Callable, Optional, Sequence, Union, Literal
7
  import inspect
8
  import re
9
 
@@ -25,7 +32,7 @@ from fastmcp.exceptions import ResourceError
25
  from fastmcp.resources import Resource, ResourceManager
26
  from fastmcp.resources.types import FunctionResource
27
  from fastmcp.tools import ToolManager
28
- from fastmcp.utilities.logging import get_logger, configure_logging
29
  from fastmcp.utilities.types import Image
30
 
31
  logger = get_logger(__name__)
@@ -368,3 +375,104 @@ def _convert_to_content(value: Any) -> Sequence[Union[TextContent, ImageContent]
368
  text=json.dumps(value, indent=2, default=pydantic.json.pydantic_encoder),
369
  )
370
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """FastMCP - A more ergonomic interface for MCP servers."""
2
 
3
+ from typing import Any, Literal, Optional, Union
4
+
5
+ from mcp.server import RequestContext
6
+ from pydantic import BaseModel
7
+ from pydantic.networks import AnyUrl
8
+
9
+ from fastmcp.utilities.logging import get_logger
10
  import asyncio
11
  import functools
12
  import json
13
+ from typing import Callable, Sequence
14
  import inspect
15
  import re
16
 
 
32
  from fastmcp.resources import Resource, ResourceManager
33
  from fastmcp.resources.types import FunctionResource
34
  from fastmcp.tools import ToolManager
35
+ from fastmcp.utilities.logging import configure_logging
36
  from fastmcp.utilities.types import Image
37
 
38
  logger = get_logger(__name__)
 
375
  text=json.dumps(value, indent=2, default=pydantic.json.pydantic_encoder),
376
  )
377
  ]
378
+
379
+
380
+ class Context(BaseModel):
381
+ """Context object providing access to MCP capabilities.
382
+
383
+ This provides a cleaner interface to MCP's RequestContext functionality.
384
+ It gets injected into tool and resource functions that request it via type hints.
385
+ """
386
+
387
+ _request_context: RequestContext
388
+ fastmcp: FastMCP
389
+
390
+ model_config: dict = dict(arbitrary_types_allowed=True)
391
+
392
+ async def report_progress(
393
+ self, progress: float, total: Optional[float] = None
394
+ ) -> None:
395
+ """Report progress for the current operation.
396
+
397
+ Args:
398
+ progress: Current progress value e.g. 24
399
+ total: Optional total value e.g. 100
400
+ """
401
+
402
+ progress_token = (
403
+ self._request_context.meta.progressToken
404
+ if self._request_context.meta
405
+ else None
406
+ )
407
+
408
+ if not progress_token:
409
+ return
410
+
411
+ await self._request_context.session.send_progress_notification(
412
+ progress_token=progress_token, progress=progress, total=total
413
+ )
414
+
415
+ async def read_resource(self, uri: Union[str, AnyUrl]) -> Union[str, bytes]:
416
+ """Read a resource by URI.
417
+
418
+ Args:
419
+ uri: Resource URI to read
420
+
421
+ Returns:
422
+ The resource content as either text or bytes
423
+ """
424
+ return await self.fastmcp.read_resource(uri)
425
+
426
+ def log(
427
+ self,
428
+ level: Literal["debug", "info", "warning", "error"],
429
+ message: str,
430
+ *,
431
+ logger_name: Optional[str] = None,
432
+ **extra: Any,
433
+ ) -> None:
434
+ """Send a log message to the client.
435
+
436
+ Args:
437
+ level: Log level (debug, info, warning, error)
438
+ message: Log message
439
+ logger_name: Optional logger name
440
+ **extra: Additional structured data to include
441
+ """
442
+ self._request_context.session.send_log_message(
443
+ level=level, data=message, logger=logger_name, extra=extra
444
+ )
445
+
446
+ @property
447
+ def client_id(self) -> Optional[str]:
448
+ """Get the client ID if available."""
449
+ return (
450
+ self._request_context.meta.clientId if self._request_context.meta else None
451
+ )
452
+
453
+ @property
454
+ def request_id(self) -> str:
455
+ """Get the unique ID for this request."""
456
+ return self._request_context.request_id
457
+
458
+ @property
459
+ def session(self):
460
+ """Access to the underlying session for advanced usage."""
461
+ return self._request_context.session
462
+
463
+ # Convenience methods for common log levels
464
+ def debug(self, message: str, **extra: Any) -> None:
465
+ """Send a debug log message."""
466
+ self.log("debug", message, **extra)
467
+
468
+ def info(self, message: str, **extra: Any) -> None:
469
+ """Send an info log message."""
470
+ self.log("info", message, **extra)
471
+
472
+ def warning(self, message: str, **extra: Any) -> None:
473
+ """Send a warning log message."""
474
+ self.log("warning", message, **extra)
475
+
476
+ def error(self, message: str, **extra: Any) -> None:
477
+ """Send an error log message."""
478
+ self.log("error", message, **extra)