agentenv-framework-protocol 0.1.350__tar.gz → 0.1.352__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.
Files changed (49) hide show
  1. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/.gitignore +3 -0
  2. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/PKG-INFO +26 -1
  3. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/README.md +24 -0
  4. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/pyproject.toml +2 -1
  5. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/__init__.py +2 -0
  6. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/a2a_agent/extensions.py +2 -2
  7. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/a2a_agent/framework.py +69 -4
  8. agentenv_framework_protocol-0.1.352/src/agentenv_protocol/a2a_agent/peer_agents.py +422 -0
  9. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/agent_env_environment.py +16 -2
  10. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/transfers.py +11 -4
  11. agentenv_framework_protocol-0.1.352/tests/test_a2a_peer_agents.py +338 -0
  12. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_agent_env_environment.py +44 -0
  13. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/LICENSE +0 -0
  14. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/NOTICE +0 -0
  15. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/THIRD_PARTY_NOTICES.md +0 -0
  16. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/examples/__init__.py +0 -0
  17. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/examples/advanced_agent.py +0 -0
  18. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/examples/basic_agent.py +0 -0
  19. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/examples/custom_extensions_agent.py +0 -0
  20. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/examples/multimodal_agent.py +0 -0
  21. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/examples/reference_model.py +0 -0
  22. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/examples/streaming_agent.py +0 -0
  23. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
  24. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
  25. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
  26. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
  27. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
  28. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
  29. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
  30. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/client.py +0 -0
  31. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/manifest.py +0 -0
  32. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/preflight.py +0 -0
  33. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/tool_client.py +0 -0
  34. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/tool_context.py +0 -0
  35. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/src/agentenv_protocol/types.py +0 -0
  36. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_a2a_agent.py +0 -0
  37. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_a2a_agent_examples.py +0 -0
  38. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_a2a_object_transfers.py +0 -0
  39. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_a2a_staging.py +0 -0
  40. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_a2a_trajectory_log.py +0 -0
  41. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_client.py +0 -0
  42. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_data_objects.py +0 -0
  43. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_intake.py +0 -0
  44. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_manifest.py +0 -0
  45. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_preflight.py +0 -0
  46. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_tool_client.py +0 -0
  47. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_tool_context.py +0 -0
  48. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_tool_context_fastmcp.py +0 -0
  49. {agentenv_framework_protocol-0.1.350 → agentenv_framework_protocol-0.1.352}/tests/test_tool_context_injection.py +0 -0
@@ -167,3 +167,6 @@ cython_debug/
167
167
 
168
168
  # Generated local-store state (stores default local; see runtime._ensure_agentenv_gitignore)
169
169
  /.agentenv/
170
+
171
+ # Written by the release workflow into the wheels it publishes
172
+ src/agent_env/env/published_images.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentenv-framework-protocol
3
- Version: 0.1.350
3
+ Version: 0.1.352
4
4
  Summary: Open data-plane protocol and server SDK for agent environments.
5
5
  Project-URL: Homepage, https://www.agentenvframework.com
6
6
  Project-URL: Documentation, https://www.agentenvframework.com/docs
@@ -30,6 +30,7 @@ Requires-Dist: pydantic>=2.11
30
30
  Requires-Dist: starlette>=0.37
31
31
  Provides-Extra: agent
32
32
  Requires-Dist: a2a-sdk[http-server]<0.4,>=0.3.26; extra == 'agent'
33
+ Requires-Dist: mcp<2,>=1.25; extra == 'agent'
33
34
  Requires-Dist: python-multipart>=0.0.13; extra == 'agent'
34
35
  Requires-Dist: regex>=2023.12.25; extra == 'agent'
35
36
  Requires-Dist: uvicorn>=0.30; extra == 'agent'
@@ -126,6 +127,15 @@ environment-prefixed tools without knowing the name at class-definition time; an
126
127
  servers an env deploys behind one gateway: if two serve a name, the gateway serves none of their
127
128
  tools and the deploy fails naming it, so prefix with `{environment_name}` when servers may share one.
128
129
 
130
+ `@tool(..., hidden_by_default=True)` makes being hidden a fixed property of the tool, for plumbing a
131
+ task drives (a trigger's tool action, a simulated party) that the agent under test must not see. The
132
+ tool is listed with `_meta` `{"agentenv.io/hiddenByDefault": true}` (`HIDDEN_BY_DEFAULT_META_KEY`),
133
+ and the gateway hides it from every role, in its MCP `tools/list` and `/step`, until a tool-access
134
+ step names it. Steps that name it show and hide it as they do any tool. Steps for every tool
135
+ (`tools: "*"`) never show it, but as with any tool they replace earlier steps: one for a role clears
136
+ that role's earlier named steps, and a full reset (`role: "*", tools: "*"`) clears all of them, so
137
+ grant a hidden tool after such steps. Trigger tool actions call it as usual.
138
+
129
139
  `AgentEnvStarletteApplication` mounts the same handler onto a Starlette/FastAPI app instead of
130
140
  FastMCP; since those apps have no MCP tool registry, constructing one with `@tool` methods
131
141
  raises.
@@ -706,6 +716,21 @@ tools with (agent-env relays the env card's name: the MultiEnv's declared name,
706
716
  `env` + 4 random digits, giving e.g. `mcp__env4821__<tool>`); when absent the agent mints
707
717
  `mcp_<8 hex>`.
708
718
 
719
+ `PEER_AGENTS_V1` lets an agent delegate to the other agents in its task. AgentEnv
720
+ sets the routing table with `POST /ext/peer-agents` (`{"peers": [{"name", "url",
721
+ "card", "description"}]}`; an empty list clears it, and names must be unique), and
722
+ `GET /ext/peer-agents` returns it. An agent that enables the extension without
723
+ handlers of its own gets the SDK's: the SDK serves six MCP tools, `peer_list`,
724
+ `peer_get_card`, `peer_send_message` (send and wait, for at most 50 s, so a call
725
+ answers within the 60 s MCP clients commonly give a request), `peer_start_task`,
726
+ `peer_get_task` and `peer_cancel_task`, at `/ext/peer-agents/mcp` on the agent's
727
+ own port, and adds them to every task's `request.mcp_servers` as `peer_agents`, so
728
+ a harness that connects the task's MCP servers gives its model the tools with no
729
+ code of its own. The tools wrap the peers' A2A `message/send`, `tasks/get` and
730
+ `tasks/cancel`; they answer loopback callers only, while the application's lifespan
731
+ runs, and the name `peer_agents` is reserved from `MCP_CONFIG_V1.add`. An agent that
732
+ overrides both operations keeps the table itself and gets no tools.
733
+
709
734
  `ATTRIBUTION_PROBE_V1.probe` lets a caller check that an agent forwards attribution
710
735
  upstream. Attribution is an open map of string dimensions, like AgentEnv's cost
711
736
  attribution: the SDK assigns no keys, and each deployment uses whatever dimensions
@@ -81,6 +81,15 @@ environment-prefixed tools without knowing the name at class-definition time; an
81
81
  servers an env deploys behind one gateway: if two serve a name, the gateway serves none of their
82
82
  tools and the deploy fails naming it, so prefix with `{environment_name}` when servers may share one.
83
83
 
84
+ `@tool(..., hidden_by_default=True)` makes being hidden a fixed property of the tool, for plumbing a
85
+ task drives (a trigger's tool action, a simulated party) that the agent under test must not see. The
86
+ tool is listed with `_meta` `{"agentenv.io/hiddenByDefault": true}` (`HIDDEN_BY_DEFAULT_META_KEY`),
87
+ and the gateway hides it from every role, in its MCP `tools/list` and `/step`, until a tool-access
88
+ step names it. Steps that name it show and hide it as they do any tool. Steps for every tool
89
+ (`tools: "*"`) never show it, but as with any tool they replace earlier steps: one for a role clears
90
+ that role's earlier named steps, and a full reset (`role: "*", tools: "*"`) clears all of them, so
91
+ grant a hidden tool after such steps. Trigger tool actions call it as usual.
92
+
84
93
  `AgentEnvStarletteApplication` mounts the same handler onto a Starlette/FastAPI app instead of
85
94
  FastMCP; since those apps have no MCP tool registry, constructing one with `@tool` methods
86
95
  raises.
@@ -661,6 +670,21 @@ tools with (agent-env relays the env card's name: the MultiEnv's declared name,
661
670
  `env` + 4 random digits, giving e.g. `mcp__env4821__<tool>`); when absent the agent mints
662
671
  `mcp_<8 hex>`.
663
672
 
673
+ `PEER_AGENTS_V1` lets an agent delegate to the other agents in its task. AgentEnv
674
+ sets the routing table with `POST /ext/peer-agents` (`{"peers": [{"name", "url",
675
+ "card", "description"}]}`; an empty list clears it, and names must be unique), and
676
+ `GET /ext/peer-agents` returns it. An agent that enables the extension without
677
+ handlers of its own gets the SDK's: the SDK serves six MCP tools, `peer_list`,
678
+ `peer_get_card`, `peer_send_message` (send and wait, for at most 50 s, so a call
679
+ answers within the 60 s MCP clients commonly give a request), `peer_start_task`,
680
+ `peer_get_task` and `peer_cancel_task`, at `/ext/peer-agents/mcp` on the agent's
681
+ own port, and adds them to every task's `request.mcp_servers` as `peer_agents`, so
682
+ a harness that connects the task's MCP servers gives its model the tools with no
683
+ code of its own. The tools wrap the peers' A2A `message/send`, `tasks/get` and
684
+ `tasks/cancel`; they answer loopback callers only, while the application's lifespan
685
+ runs, and the name `peer_agents` is reserved from `MCP_CONFIG_V1.add`. An agent that
686
+ overrides both operations keeps the table itself and gets no tools.
687
+
664
688
  `ATTRIBUTION_PROBE_V1.probe` lets a caller check that an agent forwards attribution
665
689
  upstream. Attribution is an open map of string dimensions, like AgentEnv's cost
666
690
  attribution: the SDK assigns no keys, and each deployment uses whatever dimensions
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentenv-framework-protocol"
3
- version = "0.1.350"
3
+ version = "0.1.352"
4
4
  description = "Open data-plane protocol and server SDK for agent environments."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -30,6 +30,7 @@ dependencies = [
30
30
  [project.optional-dependencies]
31
31
  agent = [
32
32
  "a2a-sdk[http-server]>=0.3.26,<0.4",
33
+ "mcp>=1.25,<2", # the peer-agents tools' MCP server; 2.0 removed the server API it serves them with
33
34
  "python-multipart>=0.0.13", # the staging extension's upload forms
34
35
  "regex>=2023.12.25",
35
36
  "uvicorn>=0.30",
@@ -4,6 +4,7 @@ from . import client
4
4
  from .preflight import intake_fit_check
5
5
  from .tool_client import ToolResult, ToolSession, ToolSessionError
6
6
  from .agent_env_environment import (
7
+ HIDDEN_BY_DEFAULT_META_KEY,
7
8
  AgentEnvApplication,
8
9
  AgentEnvEnvironment,
9
10
  AgentEnvFastMCPApplication,
@@ -97,6 +98,7 @@ __all__ = [
97
98
  "bound",
98
99
  "ROLE_META_KEY",
99
100
  "SESSION_META_KEY",
101
+ "HIDDEN_BY_DEFAULT_META_KEY",
100
102
  "ROLE_HEADER",
101
103
  "DEFAULT_ROLE",
102
104
  "EnvironmentCapabilities",
@@ -1106,11 +1106,11 @@ PEER_AGENTS_V1 = ExtensionDefinition(
1106
1106
  "set",
1107
1107
  "POST",
1108
1108
  "/ext/peer-agents",
1109
- RUNTIME,
1109
+ SDK,
1110
1110
  request_model=PeerAgentsSetRequest,
1111
1111
  ),
1112
1112
  "list": _op(
1113
- "list", "GET", "/ext/peer-agents", RUNTIME, response_required=("peers",)
1113
+ "list", "GET", "/ext/peer-agents", SDK, response_required=("peers",)
1114
1114
  ),
1115
1115
  },
1116
1116
  )
@@ -34,6 +34,7 @@ from .extensions import (
34
34
  AGENT_CONFIG_V1,
35
35
  MAX_TRAJECTORY_READ_EVENTS,
36
36
  MCP_CONFIG_V1,
37
+ PEER_AGENTS_V1,
37
38
  SKILL_CONFIG_V1,
38
39
  TRAJECTORY_V1,
39
40
  TRIGGERS_V1,
@@ -45,6 +46,7 @@ from .extensions import (
45
46
  ImplementationOwner,
46
47
  McpAddRequest,
47
48
  OperationReference,
49
+ PeerAgentsSetRequest,
48
50
  TaskEventsTrajectoryRequest,
49
51
  TaskObjectTrajectoryRequest,
50
52
  TaskTrajectoryRequest,
@@ -54,6 +56,7 @@ from .extensions import (
54
56
  TriggerRegisterRequest,
55
57
  enable,
56
58
  )
59
+ from .peer_agents import PEER_MCP_PATH, PEER_MCP_SERVER_NAME, PeerAgents
57
60
  from .registry import RegisteredOperation, build_registry
58
61
  from .staging import STAGING_ENDPOINT, StagingStore, staging_routes
59
62
  from .tasks.v1 import (
@@ -545,6 +548,8 @@ class _SdkServices:
545
548
  self._skill_registration_lock = asyncio.Lock()
546
549
  self.card: Any = None
547
550
  self.trigger_engine = TriggerEngine()
551
+ # Set when the SDK serves peer-agents/v1 for this agent.
552
+ self.peers: PeerAgents | None = None
548
553
 
549
554
  def task_config(self) -> AgentConfig:
550
555
  return self.config_model.model_validate(
@@ -611,6 +616,8 @@ class _SdkServices:
611
616
  (AGENT_CONFIG_V1.uri, "get"): self.agent_config_get,
612
617
  (MCP_CONFIG_V1.uri, "add"): self.mcp_add,
613
618
  (MCP_CONFIG_V1.uri, "list"): self.mcp_list,
619
+ (PEER_AGENTS_V1.uri, "set"): self.peer_agents_set,
620
+ (PEER_AGENTS_V1.uri, "list"): self.peer_agents_list,
614
621
  (SKILL_CONFIG_V1.uri, "list"): self.skill_list,
615
622
  (TRAJECTORY_V1.uri, "get"): self.trajectory_get,
616
623
  (TRIGGERS_V1.uri, "register"): self.triggers_register,
@@ -695,6 +702,8 @@ class _SdkServices:
695
702
  name = request.name or f"mcp_{uuid.uuid4().hex[:8]}"
696
703
  if name in self.mcp_servers:
697
704
  raise HTTPException(status_code=409, detail=f"MCP name '{name}' is already registered")
705
+ if self.peers is not None and name == PEER_MCP_SERVER_NAME:
706
+ raise HTTPException(status_code=409, detail=f"MCP name '{name}' is the SDK's peer-agents server")
698
707
  self.mcp_servers[name] = {
699
708
  "url": url,
700
709
  "headers": dict(request.headers) if request.headers else None,
@@ -708,10 +717,30 @@ class _SdkServices:
708
717
  "url": registration["url"],
709
718
  "has_headers": bool(registration.get("headers")),
710
719
  }
711
- for name, registration in self.mcp_servers.items()
720
+ for name, registration in self.task_mcp_servers().items()
712
721
  }
713
722
  }
714
723
 
724
+ def task_mcp_servers(self) -> dict[str, dict[str, Any]]:
725
+ """The MCP servers a task gets: those registered, and the SDK's peer-agents server when it serves one."""
726
+ peer_tools = self.peers.registration() if self.peers is not None else None
727
+ return {**self.mcp_servers, **({PEER_MCP_SERVER_NAME: peer_tools} if peer_tools else {})}
728
+
729
+ async def peer_agents_set(self, request: PeerAgentsSetRequest) -> dict[str, Any]:
730
+ names = [peer.name for peer in request.peers]
731
+ if len(set(names)) != len(names):
732
+ raise HTTPException(status_code=400, detail="peer names must be unique")
733
+ self._served_peers().set(request.peers)
734
+ return {"status": "updated", "peers": len(names)}
735
+
736
+ async def peer_agents_list(self) -> dict[str, Any]:
737
+ return {"peers": self._served_peers().listing()}
738
+
739
+ def _served_peers(self) -> PeerAgents:
740
+ if self.peers is None:
741
+ raise HTTPException(status_code=500, detail="the SDK serves no peer-agents tools for this agent")
742
+ return self.peers
743
+
715
744
  async def skill_list(self) -> dict[str, Any]:
716
745
  skills = {
717
746
  name: {"description": description}
@@ -874,6 +903,24 @@ class DefaultExtensionHandlers:
874
903
  return await _invoke(handler, request)
875
904
 
876
905
 
906
+ def _serving_peer_tools(
907
+ peers: PeerAgents,
908
+ inner: Callable[[Starlette], AbstractAsyncContextManager] | None,
909
+ ) -> Callable[[Starlette], AbstractAsyncContextManager]:
910
+ """The application's lifespan, ``inner``'s if it has one, with the peer-agents tools served throughout."""
911
+
912
+ @asynccontextmanager
913
+ async def lifespan(app: Starlette) -> AsyncIterator[Any]:
914
+ async with peers.serving():
915
+ if inner is None:
916
+ yield None
917
+ else:
918
+ async with inner(app) as state:
919
+ yield state
920
+
921
+ return lifespan
922
+
923
+
877
924
  class A2AAgentApplication:
878
925
  """Resolved Agent Card, extension routes, and A2A task executor."""
879
926
 
@@ -930,6 +977,20 @@ class A2AAgentApplication:
930
977
  f"extension operation {extension.definition.uri}.{operation.definition.name} "
931
978
  f"conflicts with the staging routes below {STAGING_ENDPOINT}"
932
979
  )
980
+ # The SDK serves peer-agents/v1's tools to an agent that enables it without handlers of its own.
981
+ peer_agents = next(
982
+ (extension for extension in self.registry.extensions if extension.definition.uri == PEER_AGENTS_V1.uri),
983
+ None,
984
+ )
985
+ if peer_agents is not None and not peer_agents.operations["set"].overridden:
986
+ for extension in self.registry.extensions:
987
+ for operation in extension.operations.values():
988
+ if operation.definition.path == PEER_MCP_PATH:
989
+ raise ValueError(
990
+ f"extension operation {extension.definition.uri}.{operation.definition.name} "
991
+ f"conflicts with the peer-agents tools at {PEER_MCP_PATH}"
992
+ )
993
+ self.services.peers = PeerAgents()
933
994
  conformance = self.registry.conformance()
934
995
  for override in conformance["standard_operation_overrides"]:
935
996
  logger.warning(
@@ -965,9 +1026,13 @@ class A2AAgentApplication:
965
1026
  )
966
1027
  if self.staging.max_bytes:
967
1028
  routes.extend(staging_routes(self.staging))
1029
+ lifespan = definition.lifespan
1030
+ if self.services.peers is not None:
1031
+ routes.append(Route(PEER_MCP_PATH, self.services.peers.app))
1032
+ lifespan = _serving_peer_tools(self.services.peers, lifespan)
968
1033
  kwargs = {"routes": routes, "middleware": [Middleware(_ServingOnItsPort)]}
969
- if definition.lifespan is not None:
970
- kwargs["lifespan"] = definition.lifespan
1034
+ if lifespan is not None:
1035
+ kwargs["lifespan"] = lifespan
971
1036
  self.app = Starlette(**kwargs)
972
1037
 
973
1038
  try:
@@ -1171,7 +1236,7 @@ class _StandardExecutor:
1171
1236
  _from_a2a_part(part) for part in (context.message.parts or [])
1172
1237
  ),
1173
1238
  config=task_config,
1174
- mcp_servers=self._services.mcp_servers,
1239
+ mcp_servers=self._services.task_mcp_servers(),
1175
1240
  skills=tuple(self._services.skills),
1176
1241
  metadata=metadata,
1177
1242
  session_ref=self._services.session_ref_for_context(task.context_id),