aixtools 0.2.18__tar.gz → 0.2.20__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aixtools might be problematic. Click here for more details.

Files changed (102) hide show
  1. {aixtools-0.2.18 → aixtools-0.2.20}/PKG-INFO +1 -1
  2. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/_version.py +3 -3
  3. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/a2a/google_sdk/utils.py +15 -7
  4. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/mcp/__init__.py +5 -1
  5. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/mcp/client.py +44 -41
  6. aixtools-0.2.18/aixtools/logging/mcp_middleware.py → aixtools-0.2.20/aixtools/mcp/middleware.py +1 -1
  7. aixtools-0.2.20/aixtools/mcp/server.py +72 -0
  8. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/server/utils.py +36 -2
  9. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools.egg-info/SOURCES.txt +2 -1
  10. {aixtools-0.2.18 → aixtools-0.2.20}/README.md +0 -0
  11. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/config.toml +0 -0
  12. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/translations/bn.json +0 -0
  13. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/translations/en-US.json +0 -0
  14. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/translations/gu.json +0 -0
  15. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/translations/he-IL.json +0 -0
  16. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/translations/hi.json +0 -0
  17. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/translations/ja.json +0 -0
  18. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/translations/kn.json +0 -0
  19. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/translations/ml.json +0 -0
  20. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/translations/mr.json +0 -0
  21. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/translations/nl.json +0 -0
  22. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/translations/ta.json +0 -0
  23. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/translations/te.json +0 -0
  24. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/.chainlit/translations/zh-CN.json +0 -0
  25. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/__init__.py +0 -0
  26. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/a2a/app.py +0 -0
  27. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/a2a/google_sdk/__init__.py +0 -0
  28. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/a2a/google_sdk/pydantic_ai_adapter/agent_executor.py +0 -0
  29. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/a2a/google_sdk/pydantic_ai_adapter/storage.py +0 -0
  30. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/a2a/google_sdk/remote_agent_connection.py +0 -0
  31. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/a2a/utils.py +0 -0
  32. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/agents/__init__.py +0 -0
  33. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/agents/agent.py +0 -0
  34. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/agents/agent_batch.py +0 -0
  35. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/agents/nodes_to_md.py +0 -0
  36. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/agents/nodes_to_message.py +0 -0
  37. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/agents/nodes_to_str.py +0 -0
  38. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/agents/print_nodes.py +0 -0
  39. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/agents/prompt.py +0 -0
  40. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/app.py +0 -0
  41. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/auth/__init__.py +0 -0
  42. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/auth/auth.py +0 -0
  43. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/chainlit.md +0 -0
  44. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/compliance/__init__.py +0 -0
  45. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/compliance/private_data.py +0 -0
  46. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/context.py +0 -0
  47. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/db/__init__.py +0 -0
  48. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/db/database.py +0 -0
  49. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/db/vector_db.py +0 -0
  50. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/evals/__init__.py +0 -0
  51. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/evals/__main__.py +0 -0
  52. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/evals/dataset.py +0 -0
  53. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/evals/discovery.py +0 -0
  54. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/evals/run_evals.py +0 -0
  55. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/google/client.py +0 -0
  56. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/log_view/__init__.py +0 -0
  57. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/log_view/app.py +0 -0
  58. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/log_view/display.py +0 -0
  59. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/log_view/export.py +0 -0
  60. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/log_view/filters.py +0 -0
  61. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/log_view/log_utils.py +0 -0
  62. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/log_view/node_summary.py +0 -0
  63. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/logfilters/__init__.py +0 -0
  64. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/logfilters/context_filter.py +0 -0
  65. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/logging/__init__.py +0 -0
  66. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/logging/log_objects.py +0 -0
  67. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/logging/logging_config.py +0 -0
  68. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/logging/mcp_log_models.py +0 -0
  69. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/logging/mcp_logger.py +0 -0
  70. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/logging/model_patch_logging.py +0 -0
  71. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/logging/open_telemetry.py +0 -0
  72. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/mcp/example_client.py +0 -0
  73. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/mcp/example_server.py +0 -0
  74. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/mcp/exceptions.py +0 -0
  75. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/mcp/fast_mcp_log.py +0 -0
  76. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/mcp/faulty_mcp.py +0 -0
  77. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/model_patch/model_patch.py +0 -0
  78. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/server/__init__.py +0 -0
  79. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/server/app_mounter.py +0 -0
  80. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/server/path.py +0 -0
  81. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/testing/__init__.py +0 -0
  82. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/testing/agent_mock.py +0 -0
  83. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/testing/aix_test_model.py +0 -0
  84. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/testing/mock_tool.py +0 -0
  85. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/testing/model_patch_cache.py +0 -0
  86. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/tools/doctor/__init__.py +0 -0
  87. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/tools/doctor/mcp_tool_doctor.py +0 -0
  88. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/tools/doctor/tool_doctor.py +0 -0
  89. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/tools/doctor/tool_recommendation.py +0 -0
  90. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/utils/__init__.py +0 -0
  91. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/utils/chainlit/cl_agent_show.py +0 -0
  92. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/utils/chainlit/cl_utils.py +0 -0
  93. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/utils/config.py +0 -0
  94. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/utils/config_util.py +0 -0
  95. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/utils/enum_with_description.py +0 -0
  96. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/utils/files.py +0 -0
  97. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/utils/persisted_dict.py +0 -0
  98. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/utils/utils.py +0 -0
  99. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/vault/__init__.py +0 -0
  100. {aixtools-0.2.18 → aixtools-0.2.20}/aixtools/vault/vault.py +0 -0
  101. {aixtools-0.2.18 → aixtools-0.2.20}/pyproject.toml +0 -0
  102. {aixtools-0.2.18 → aixtools-0.2.20}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aixtools
3
- Version: 0.2.18
3
+ Version: 0.2.20
4
4
  Summary: Tools for AI exploration and debugging
5
5
  Requires-Python: >=3.11.2
6
6
  Description-Content-Type: text/markdown
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.2.18'
32
- __version_tuple__ = version_tuple = (0, 2, 18)
31
+ __version__ = version = '0.2.20'
32
+ __version_tuple__ = version_tuple = (0, 2, 20)
33
33
 
34
- __commit_id__ = commit_id = 'gda0a23c48'
34
+ __commit_id__ = commit_id = 'ge7f4f14bb'
@@ -11,6 +11,7 @@ from a2a.utils import AGENT_CARD_WELL_KNOWN_PATH, PREV_AGENT_CARD_WELL_KNOWN_PAT
11
11
  from aixtools.a2a.google_sdk.remote_agent_connection import RemoteAgentConnection
12
12
  from aixtools.context import DEFAULT_SESSION_ID, DEFAULT_USER_ID, SessionIdTuple
13
13
  from aixtools.logging.logging_config import get_logger
14
+ from aixtools.server.utils import create_session_headers
14
15
 
15
16
  logger = get_logger(__name__)
16
17
 
@@ -23,6 +24,7 @@ class AgentCardLoadFailedError(Exception):
23
24
 
24
25
  async def get_agent_card(client: httpx.AsyncClient, address: str) -> AgentCard:
25
26
  """Retrieve the agent card from the given agent address."""
27
+ warnings = []
26
28
  for card_path in [AGENT_CARD_WELL_KNOWN_PATH, PREV_AGENT_CARD_WELL_KNOWN_PATH]:
27
29
  try:
28
30
  card_resolver = A2ACardResolver(client, address, card_path)
@@ -30,8 +32,10 @@ async def get_agent_card(client: httpx.AsyncClient, address: str) -> AgentCard:
30
32
  card.url = address
31
33
  return card
32
34
  except Exception as e:
33
- logger.warning(f"Error retrieving agent card from {address} at path {card_path}: {e}")
35
+ warnings.append(f"Error retrieving agent card from {address} at path {card_path}: {e}")
34
36
 
37
+ for warning in warnings:
38
+ logger.warning(warning)
35
39
  raise AgentCardLoadFailedError(f"Failed to load agent card from {address}")
36
40
 
37
41
 
@@ -65,15 +69,19 @@ class _AgentCardResolver:
65
69
 
66
70
 
67
71
  async def get_a2a_clients(
68
- ctx: SessionIdTuple, agent_hosts: list[str], *, timeout: float = DEFAULT_A2A_TIMEOUT
72
+ agent_hosts: list[str],
73
+ session_id_tuple: SessionIdTuple,
74
+ auth_token: str = None,
75
+ *,
76
+ timeout: float = DEFAULT_A2A_TIMEOUT,
69
77
  ) -> dict[str, RemoteAgentConnection]:
70
78
  """Get A2A clients for all agents defined in the configuration."""
71
- headers = {
72
- "user-id": ctx[0],
73
- "session-id": ctx[1],
74
- }
79
+ headers = create_session_headers(session_id_tuple, auth_token)
75
80
  httpx_client = httpx.AsyncClient(headers=headers, timeout=timeout, follow_redirects=True)
76
- return await _AgentCardResolver(httpx_client).get_a2a_clients(agent_hosts)
81
+ clients = await _AgentCardResolver(httpx_client).get_a2a_clients(agent_hosts)
82
+ for client in clients.values():
83
+ logger.info("Using A2A server at: %s", client.get_agent_card().url)
84
+ return clients
77
85
 
78
86
 
79
87
  def card2description(card: AgentCard) -> str:
@@ -4,8 +4,12 @@ Model Context Protocol (MCP) implementation for AI agent communication.
4
4
 
5
5
  from aixtools.mcp.exceptions import AixToolError
6
6
  from aixtools.mcp.fast_mcp_log import FastMcpLog
7
+ from aixtools.mcp.middleware import AixErrorHandlingMiddleware
8
+ from aixtools.mcp.server import create_mcp_server
7
9
 
8
10
  __all__ = [
9
- "FastMcpLog",
11
+ "AixErrorHandlingMiddleware",
10
12
  "AixToolError",
13
+ "FastMcpLog",
14
+ "create_mcp_server",
11
15
  ]
@@ -3,6 +3,7 @@
3
3
  import asyncio
4
4
  import logging
5
5
  from contextlib import asynccontextmanager
6
+ from dataclasses import dataclass, field
6
7
  from datetime import timedelta
7
8
  from typing import Any, AsyncGenerator
8
9
 
@@ -21,6 +22,7 @@ from pydantic_ai.toolsets.abstract import ToolsetTool
21
22
 
22
23
  from aixtools.context import SessionIdTuple
23
24
  from aixtools.logging.logging_config import get_logger
25
+ from aixtools.server.utils import create_session_headers
24
26
 
25
27
  MCP_TOOL_CACHE_TTL = 300 # 5 minutes
26
28
  DEFAULT_MCP_CONNECTION_TIMEOUT = 30
@@ -49,6 +51,14 @@ async def default_mcp_log_handler(message: LogMessage):
49
51
  logger.log(level, msg, extra=extra)
50
52
 
51
53
 
54
+ @dataclass
55
+ class MCPConfig:
56
+ """Configuration for an MCP server retrieved from config.yaml"""
57
+
58
+ url: str
59
+ read_timeout: float = field(default=DEFAULT_MCP_READ_TIMEOUT)
60
+
61
+
52
62
  def get_mcp_client(
53
63
  url: str | None = None,
54
64
  command: str | None = None,
@@ -73,57 +83,50 @@ def get_mcp_client(
73
83
  raise ValueError("Either url or command must be provided to create MCP client.")
74
84
 
75
85
 
76
- def get_mcp_headers(session_id_tuple: SessionIdTuple) -> dict[str, str] | None:
86
+ def get_mcp_servers(
87
+ mcp_configs: list[MCPConfig],
88
+ session_id_tuple: SessionIdTuple,
89
+ auth_token: str = None,
90
+ *,
91
+ timeout: float = DEFAULT_MCP_CONNECTION_TIMEOUT,
92
+ ):
77
93
  """
78
- Generate headers for MCP server requests.
94
+ Create cached MCP server instances with robust error handling and isolation.
79
95
 
80
- This function creates a dictionary of headers to be used in requests to
81
- the MCP servers. If a `user_id` or `session_id` is provided, they are
82
- included in the headers.
96
+ This function creates and returns a list of `CachedMCPServerStreamableHTTP` instances
97
+ based on the provided URLs. Each server instance includes:
98
+ - TTL-based caching for tool lists (5 minutes default)
99
+ - Complete task isolation to prevent cancellation propagation
100
+ - Comprehensive error handling and fallback mechanisms
101
+ - Optional user/session headers for request authentication
83
102
 
84
103
  Args:
85
- session_id_tuple (SessionIdTuple): user_id and session_id tuple
104
+ mcp_configs (list[MCPConfig]): A list of MCP server configurations to use.
105
+ session_id_tuple (SessionIdTuple): A tuple containing (user_id, session_id).
106
+ auth_token (str, optional): The authentication token for the user. Defaults to None.
107
+ timeout (float, optional): Timeout in seconds for MCP server connections.
86
108
  Returns:
87
- dict[str, str] | None: A dictionary of headers for MCP server requests,
88
- or None if neither user_id nor session_id is
89
- provided. When None is returned, default headers
90
- from the client or transport will be used.
109
+ list[CachedMCPServerStreamableHTTP]: List of cached MCP server instances with
110
+ isolation and error handling. Each server
111
+ operates independently - failures in one
112
+ server won't affect others.
91
113
  """
92
- headers = None
93
- user_id, session_id = session_id_tuple
94
- if session_id or user_id:
95
- headers = {}
96
- if session_id:
97
- headers["session-id"] = session_id
98
- if user_id:
99
- headers["user-id"] = user_id
100
- return headers
114
+ headers = create_session_headers(session_id_tuple, auth_token)
115
+ servers = []
116
+ for config in mcp_configs:
117
+ server = CachedMCPServerStreamableHTTP(
118
+ url=config.url, headers=headers, timeout=timeout, read_timeout=config.read_timeout
119
+ )
120
+ logger.info("Using MCP server at %s", config.url)
121
+ servers.append(server)
122
+ return servers
101
123
 
102
124
 
103
125
  def get_configured_mcp_servers(
104
126
  session_id_tuple: SessionIdTuple, mcp_urls: list[str], timeout: int = DEFAULT_MCP_CONNECTION_TIMEOUT
105
127
  ):
106
- """
107
- Retrieve the configured MCP server instances with optional caching.
108
-
109
- Context values `user_id` and `session_id` are included in the headers for each server request.
110
-
111
- Each server is wrapped in a try-except block to isolate them from each other.
112
- If one server fails, it won't affect the others.
113
-
114
- Args:
115
- session_id_tuple (SessionIdTuple): A tuple containing (user_id, session_id).
116
- mcp_urls: (list[str], optional): A list of MCP server URLs to use.
117
- timeout (int, optional): Timeout in seconds for MCP server connections. Defaults to 30 seconds.
118
- Returns:
119
- list[MCPServerStreamableHTTP]: A list of configured MCP server instances. If
120
- neither user_id nor session_id is provided, the
121
- server instances will use default headers defined
122
- by the underlying HTTP implementation.
123
- """
124
- headers = get_mcp_headers(session_id_tuple)
125
-
126
- return [CachedMCPServerStreamableHTTP(url=url, headers=headers, timeout=timeout) for url in mcp_urls]
128
+ """Create MCP server instances from a list of URLs."""
129
+ return get_mcp_servers([MCPConfig(url=url) for url in mcp_urls], session_id_tuple, timeout=timeout)
127
130
 
128
131
 
129
132
  class CachedMCPServerStreamableHTTP(MCPServerStreamableHTTP):
@@ -259,7 +262,7 @@ class CachedMCPServerStreamableHTTP(MCPServerStreamableHTTP):
259
262
 
260
263
  # First, check if we have a valid cached result
261
264
  if CACHE_KEY in self._tools_cache:
262
- logger.info("Using cached tools for %s", self.url)
265
+ logger.debug("Using cached tools for %s", self.url)
263
266
  return self._tools_cache[CACHE_KEY]
264
267
 
265
268
  # Create isolated task to prevent cancellation propagation
@@ -7,7 +7,7 @@ import traceback
7
7
  from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware
8
8
  from fastmcp.server.middleware.middleware import MiddlewareContext
9
9
 
10
- from aixtools.mcp import AixToolError
10
+ from aixtools.mcp.exceptions import AixToolError
11
11
 
12
12
 
13
13
  class AixErrorHandlingMiddleware(ErrorHandlingMiddleware):
@@ -0,0 +1,72 @@
1
+ """Utilities for FastMCP servers."""
2
+
3
+ from typing import Any
4
+
5
+ from fastmcp import FastMCP
6
+ from fastmcp.server.middleware.logging import LoggingMiddleware
7
+ from fastmcp.server.middleware.timing import TimingMiddleware
8
+ from fastmcp.utilities.types import NotSet
9
+
10
+ from aixtools.auth.auth import AccessTokenAuthProvider
11
+ from aixtools.logging.logging_config import get_logger
12
+ from aixtools.mcp.middleware import AixErrorHandlingMiddleware
13
+
14
+
15
+ def create_mcp_server(
16
+ *,
17
+ name: str,
18
+ instructions: str | None = None,
19
+ **kwargs: Any,
20
+ ) -> FastMCP:
21
+ """
22
+ MCP server instance with preconfigured auth and middleware.
23
+
24
+ All FastMCP constructor parameters are supported via **kwargs.
25
+
26
+ Args:
27
+ name: Server name
28
+ instructions: Optional server instructions
29
+ **kwargs: All other FastMCP constructor parameters:
30
+ - version: str | None
31
+ - auth: AuthProvider | None (AccessTokenAuthProvider if not set, pass None to disable)
32
+ - middleware: list[Middleware] | None (custom middleware if not set, pass None to disable)
33
+ - lifespan: Callable | None
34
+ - tool_serializer: Callable[[Any], str] | None
35
+ - cache_expiration_seconds: float | None
36
+ - on_duplicate_tools: DuplicateBehavior | None
37
+ - on_duplicate_resources: DuplicateBehavior | None
38
+ - on_duplicate_prompts: DuplicateBehavior | None
39
+ - resource_prefix_format: Literal["protocol", "path"] | None
40
+ - mask_error_details: bool | None
41
+ - tools: list[Tool | Callable] | None
42
+ - tool_transformations: dict[str, ToolTransformConfig] | None
43
+ - dependencies: list[str] | None
44
+ - include_tags: set[str] | None
45
+ - exclude_tags: set[str] | None
46
+ - include_fastmcp_meta: bool | None
47
+
48
+ Returns:
49
+ Configured FastMCP server instance
50
+ """
51
+ middleware = kwargs.pop("middleware", NotSet)
52
+ auth = kwargs.pop("auth", NotSet)
53
+
54
+ if middleware is NotSet:
55
+ middleware = [
56
+ LoggingMiddleware(include_payloads=True, logger=get_logger("middleware.log")),
57
+ AixErrorHandlingMiddleware(include_traceback=True, logger=get_logger("middleware.err")),
58
+ TimingMiddleware(logger=get_logger("middleware.timing")),
59
+ ]
60
+
61
+ if auth is NotSet:
62
+ auth = AccessTokenAuthProvider()
63
+
64
+ mcp_args = {
65
+ "name": name,
66
+ "instructions": instructions,
67
+ "auth": auth,
68
+ "middleware": middleware,
69
+ **kwargs,
70
+ }
71
+
72
+ return FastMCP(**mcp_args)
@@ -8,10 +8,13 @@ from functools import wraps
8
8
  from fastmcp import Context
9
9
  from fastmcp.server import dependencies
10
10
 
11
- from ..context import DEFAULT_SESSION_ID, DEFAULT_USER_ID, session_id_var, user_id_var
11
+ from ..context import DEFAULT_SESSION_ID, DEFAULT_USER_ID, SessionIdTuple, session_id_var, user_id_var
12
+ from ..logging.logging_config import get_logger
12
13
 
14
+ logger = get_logger(__name__)
13
15
 
14
- def get_session_id_tuple(ctx: Context | None = None) -> tuple[str, str]:
16
+
17
+ def get_session_id_tuple(ctx: Context | None = None) -> SessionIdTuple:
15
18
  """
16
19
  Get the user and session IDs from the user session.
17
20
  If `ctx` is None, the current FastMCP request HTTP headers are used.
@@ -60,6 +63,37 @@ def get_session_id_str(ctx: Context | None = None) -> str:
60
63
  return f"{user_id}:{session_id}"
61
64
 
62
65
 
66
+ def create_session_headers(session_id_tuple: SessionIdTuple, auth_token: str | None = None) -> dict[str, str]:
67
+ """
68
+ Generate headers for MCP or A2A server requests.
69
+
70
+ This function creates a dictionary of headers to be used in requests to
71
+ the MCP servers. If a `user_id` or `session_id` is provided, they are
72
+ included in the headers.
73
+
74
+ Args:
75
+ session_id_tuple (SessionIdTuple): user_id and session_id tuple
76
+ auth_token (str | None): Optional authorization token to include in headers
77
+
78
+ Returns:
79
+ dict[str, str]: A dictionary of headers for MCP server requests.
80
+ May be empty if no user_id, session_id, or auth_token is provided.
81
+ """
82
+ headers = {}
83
+ user_id, session_id = session_id_tuple
84
+ if auth_token:
85
+ logger.debug("Using auth token for MCP server authentication for user:%s, session_id:%s", user_id, session_id)
86
+ headers["Authorization"] = f"Bearer {auth_token}"
87
+ else:
88
+ logger.warning("No auth token found to forward to MCP/A2A servers.")
89
+
90
+ if session_id:
91
+ headers["session-id"] = session_id
92
+ if user_id:
93
+ headers["user-id"] = user_id
94
+ return headers
95
+
96
+
63
97
  def run_in_thread(func):
64
98
  """decorator to run blocking function with `asyncio.to_thread`"""
65
99
 
@@ -61,7 +61,6 @@ aixtools/logging/log_objects.py
61
61
  aixtools/logging/logging_config.py
62
62
  aixtools/logging/mcp_log_models.py
63
63
  aixtools/logging/mcp_logger.py
64
- aixtools/logging/mcp_middleware.py
65
64
  aixtools/logging/model_patch_logging.py
66
65
  aixtools/logging/open_telemetry.py
67
66
  aixtools/mcp/__init__.py
@@ -71,6 +70,8 @@ aixtools/mcp/example_server.py
71
70
  aixtools/mcp/exceptions.py
72
71
  aixtools/mcp/fast_mcp_log.py
73
72
  aixtools/mcp/faulty_mcp.py
73
+ aixtools/mcp/middleware.py
74
+ aixtools/mcp/server.py
74
75
  aixtools/model_patch/model_patch.py
75
76
  aixtools/server/__init__.py
76
77
  aixtools/server/app_mounter.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes