ms-tau-sdk 2.0.10.dev50__tar.gz → 2.0.11.dev52__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.
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/CHANGELOG.md +20 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/PKG-INFO +1 -1
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/pyproject.toml +1 -1
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +5 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/backend/mcp.py +208 -20
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/errors.py +16 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/requester.py +90 -4
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/settings.py +7 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/tools/mcp_applications.py +44 -10
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/.gitignore +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/README.md +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/__init__.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/app.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/cli.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/config.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/env_file.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/logs.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/proxy.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/state.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/static/BULMA-LICENSE.txt +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/static/app.js +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/static/board.css +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/static/bulma.min.css +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/static/index.html +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/__init__.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/agent_skills/tau_security_and_access/SKILL.md +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/api/__init__.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/api/a2a.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/api/chat.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/api/conversations.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/api/dependencies.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/api/health.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/api/inspection.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/api/local_chat.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/api/models.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/api/request_identity.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/api/sessions.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/app.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/application.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/backend/__init__.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/backend/assertions.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/backend/auth.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/backend/client.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/backend/local.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/backend/models.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/backend/routes.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/cli.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/logging.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/protocols/__init__.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/protocols/a2a_failure.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/protocols/assistant_ui.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/protocols/chat_history.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/providers/__init__.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/providers/definitions.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/providers/factory.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/providers/tau_compat.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/resources/__init__.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/resources/loader.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/__init__.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/events.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/extensions.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/failures.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/live_turns.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/manager.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/observability.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/provenance.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/session.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/task_context.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/sessions/__init__.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/sessions/storage.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/skills.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/tools/__init__.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/tools/mcp_connection.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/tools/skill_read.py +0 -0
- {ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/tools/task_control.py +0 -0
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.11 — 2026-10-11
|
|
4
|
+
|
|
5
|
+
- **Every MCP connection honors per-call response deadlines
|
|
6
|
+
([#90](https://github.com/mainsequence-sdk/ms-tau-sdk/issues/90), ADR 0022 amendment).**
|
|
7
|
+
`call_tool(..., timeout_seconds=N)` can wait for delayed headers, JSON bodies and SSE data
|
|
8
|
+
beyond the backend HTTP read limit. The finite, positive override replaces the session's
|
|
9
|
+
default response wait without changing shared HTTP settings or another call's deadline.
|
|
10
|
+
Expiration and caller cancellation close that call's local response, keep the connection
|
|
11
|
+
usable, and never replay its remote operation; cancelled queued calls are skipped.
|
|
12
|
+
Initialization/catalog/resource read limits and HTTP connect/write/pool limits remain bounded.
|
|
13
|
+
A standalone platform call has no implicit runtime turn limit. Real HTTP regression tests
|
|
14
|
+
cover these behaviors for platform and generic MCP connections.
|
|
15
|
+
|
|
3
16
|
## 2.0.10 — 2026-10-09
|
|
4
17
|
|
|
18
|
+
- **Declared applications wait while waking ([#87](https://github.com/mainsequence-sdk/ms-tau-sdk/issues/87)).**
|
|
19
|
+
An access response with `state: waking` and no grant no longer fails immediately as
|
|
20
|
+
`access_unavailable`. Tau follows the platform's retry guidance until access is ready, for up
|
|
21
|
+
to `TAU_MCP_APPLICATION_READY_TIMEOUT_SECONDS` (default `120`) or the remaining turn time.
|
|
22
|
+
Cancellation and ended delegation stop the wait, refusals remain terminal, and a call is sent
|
|
23
|
+
only once access is ready. An exhausted wait reports `readiness_timeout`, distinct from the
|
|
24
|
+
tool's execution timeout. The same readiness handling applies when renewing an application token.
|
|
5
25
|
- **Application access timeouts no longer become `access_unavailable`.** Platform transport
|
|
6
26
|
timeouts retain their classification and time limit when application access is obtained or
|
|
7
27
|
renewed. The tool reports `failure: timeout` with `phase: access`; HTTP 408/504 responses
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ms-tau-sdk
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.11.dev52
|
|
4
4
|
Summary: Workspace-bound Tau application primitives for Main Sequence projects
|
|
5
5
|
Project-URL: Changelog, https://github.com/mainsequence-sdk/ms-tau-sdk/blob/development/CHANGELOG.md
|
|
6
6
|
Project-URL: Documentation, https://github.com/mainsequence-sdk/ms-tau-sdk/tree/development/docs
|
|
@@ -91,6 +91,11 @@ the application's tool, not in the Agent. A tool that declares nothing gets
|
|
|
91
91
|
`harness_agent.spec.env_vars`, the process environment, or
|
|
92
92
|
`TauSDKSettings(mcp_tool_timeout_seconds=...)`. No call outlives its turn.
|
|
93
93
|
|
|
94
|
+
Before MCP opens, an application's access may be `waking`. Tau waits according to the platform's
|
|
95
|
+
retry guidance for up to `TAU_MCP_APPLICATION_READY_TIMEOUT_SECONDS` (default `120`) or the remaining
|
|
96
|
+
turn time. This startup budget is separate from the tool's execution limit; an expired readiness
|
|
97
|
+
wait reports `readiness_timeout`. Cancellation, ended delegation and terminal refusals stop the wait.
|
|
98
|
+
|
|
94
99
|
## Invariants extensions must not replace
|
|
95
100
|
|
|
96
101
|
Extensions may change effective agent capabilities, but they must not replace or bypass:
|
|
@@ -4,14 +4,20 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import contextvars
|
|
7
|
-
|
|
7
|
+
import math
|
|
8
|
+
from collections.abc import AsyncGenerator, AsyncIterator, Callable
|
|
9
|
+
from contextlib import asynccontextmanager
|
|
8
10
|
from dataclasses import dataclass
|
|
9
11
|
from datetime import timedelta
|
|
10
|
-
from typing import Any, Literal, Self
|
|
12
|
+
from typing import Any, Literal, Self, cast
|
|
11
13
|
|
|
14
|
+
import anyio
|
|
12
15
|
import httpx
|
|
16
|
+
from anyio.abc import ObjectSendStream
|
|
17
|
+
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
13
18
|
from mcp import ClientSession, types
|
|
14
|
-
from mcp.client.streamable_http import
|
|
19
|
+
from mcp.client.streamable_http import RequestContext, StreamableHTTPTransport
|
|
20
|
+
from mcp.shared.message import SessionMessage
|
|
15
21
|
from pydantic import AnyUrl
|
|
16
22
|
from structlog.contextvars import bound_contextvars, get_contextvars
|
|
17
23
|
|
|
@@ -47,6 +53,145 @@ class _RuntimeCredentialHTTPXAuth(httpx.Auth):
|
|
|
47
53
|
yield request
|
|
48
54
|
|
|
49
55
|
|
|
56
|
+
@dataclass
|
|
57
|
+
class _ToolCall:
|
|
58
|
+
cancel_scope: anyio.CancelScope
|
|
59
|
+
request_id: types.RequestId | None = None
|
|
60
|
+
http_cancel_scope: anyio.CancelScope | None = None
|
|
61
|
+
finished: bool = False
|
|
62
|
+
|
|
63
|
+
def cancel_if_abandoned(self, result: asyncio.Future[Any]) -> None:
|
|
64
|
+
if result.cancelled():
|
|
65
|
+
self.cancel_scope.cancel()
|
|
66
|
+
|
|
67
|
+
def finish(self) -> None:
|
|
68
|
+
self.finished = True
|
|
69
|
+
if self.http_cancel_scope is not None:
|
|
70
|
+
self.http_cancel_scope.cancel()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
_tool_call: contextvars.ContextVar[_ToolCall | None] = contextvars.ContextVar(
|
|
74
|
+
"mcp_tool_call", default=None
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class _ToolCallWriteStream(ObjectSendStream[SessionMessage]):
|
|
79
|
+
"""Associate the session's request ID with its call before the transport sends it.
|
|
80
|
+
|
|
81
|
+
MCP's HTTP writer runs in another task, so the calling task's context does not reach its
|
|
82
|
+
requests. Keep this association local to the connection, without adding anything to the wire.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
stream: MemoryObjectSendStream[SessionMessage],
|
|
88
|
+
calls: dict[types.RequestId, _ToolCall],
|
|
89
|
+
) -> None:
|
|
90
|
+
self._stream = stream
|
|
91
|
+
self._calls = calls
|
|
92
|
+
|
|
93
|
+
async def send(self, item: SessionMessage) -> None:
|
|
94
|
+
message = item.message.root
|
|
95
|
+
call = _tool_call.get()
|
|
96
|
+
if (
|
|
97
|
+
call is not None
|
|
98
|
+
and isinstance(message, types.JSONRPCRequest)
|
|
99
|
+
and message.method == "tools/call"
|
|
100
|
+
):
|
|
101
|
+
call.request_id = message.id
|
|
102
|
+
self._calls[message.id] = call
|
|
103
|
+
await self._stream.send(item)
|
|
104
|
+
|
|
105
|
+
async def aclose(self) -> None:
|
|
106
|
+
await self._stream.aclose()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class _ToolCallTransport(StreamableHTTPTransport):
|
|
110
|
+
"""Give each call's POST a cancellation scope inside MCP's transport task.
|
|
111
|
+
|
|
112
|
+
Cancelling that task directly cancels MCP's whole task group. A local scope releases the
|
|
113
|
+
response socket when its call ends while leaving other requests and the connection alive.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(self, url: str, calls: dict[types.RequestId, _ToolCall]) -> None:
|
|
117
|
+
super().__init__(url)
|
|
118
|
+
self._calls = calls
|
|
119
|
+
|
|
120
|
+
async def _handle_post_request(self, ctx: RequestContext) -> None:
|
|
121
|
+
message = ctx.session_message.message.root
|
|
122
|
+
call = (
|
|
123
|
+
self._calls.get(message.id)
|
|
124
|
+
if isinstance(message, types.JSONRPCRequest) and message.method == "tools/call"
|
|
125
|
+
else None
|
|
126
|
+
)
|
|
127
|
+
if call is None:
|
|
128
|
+
await super()._handle_post_request(ctx)
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
with anyio.CancelScope() as http_cancel_scope:
|
|
133
|
+
call.http_cancel_scope = http_cancel_scope
|
|
134
|
+
if call.finished:
|
|
135
|
+
http_cancel_scope.cancel()
|
|
136
|
+
await super()._handle_post_request(ctx)
|
|
137
|
+
finally:
|
|
138
|
+
if call.request_id is not None:
|
|
139
|
+
self._calls.pop(call.request_id, None)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@asynccontextmanager
|
|
143
|
+
async def streamable_http_client(
|
|
144
|
+
url: str,
|
|
145
|
+
*,
|
|
146
|
+
http_client: httpx.AsyncClient,
|
|
147
|
+
calls: dict[types.RequestId, _ToolCall],
|
|
148
|
+
terminate_on_close: bool = False,
|
|
149
|
+
) -> AsyncIterator[
|
|
150
|
+
tuple[
|
|
151
|
+
MemoryObjectReceiveStream[SessionMessage | Exception],
|
|
152
|
+
MemoryObjectSendStream[SessionMessage],
|
|
153
|
+
Callable[[], str | None],
|
|
154
|
+
]
|
|
155
|
+
]:
|
|
156
|
+
"""Use MCP's HTTP readers/writer with call-scoped POST cancellation.
|
|
157
|
+
|
|
158
|
+
The upstream factory constructs its transport internally without an injection point.
|
|
159
|
+
Preserve its stream/task-group lifecycle while selecting our transport specialization.
|
|
160
|
+
"""
|
|
161
|
+
read_writer, read_stream = anyio.create_memory_object_stream[SessionMessage | Exception](0)
|
|
162
|
+
write_stream, write_reader = anyio.create_memory_object_stream[SessionMessage](0)
|
|
163
|
+
transport = _ToolCallTransport(url, calls)
|
|
164
|
+
async with anyio.create_task_group() as task_group:
|
|
165
|
+
try:
|
|
166
|
+
|
|
167
|
+
def start_get_stream() -> None:
|
|
168
|
+
task_group.start_soon(transport.handle_get_stream, http_client, read_writer)
|
|
169
|
+
|
|
170
|
+
task_group.start_soon(
|
|
171
|
+
transport.post_writer,
|
|
172
|
+
http_client,
|
|
173
|
+
write_reader,
|
|
174
|
+
read_writer,
|
|
175
|
+
write_stream,
|
|
176
|
+
start_get_stream,
|
|
177
|
+
task_group,
|
|
178
|
+
)
|
|
179
|
+
try:
|
|
180
|
+
yield read_stream, write_stream, transport.get_session_id
|
|
181
|
+
finally:
|
|
182
|
+
if terminate_on_close:
|
|
183
|
+
await transport.terminate_session(http_client)
|
|
184
|
+
task_group.cancel_scope.cancel()
|
|
185
|
+
finally:
|
|
186
|
+
await read_writer.aclose()
|
|
187
|
+
await write_stream.aclose()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _validate_response_timeout(seconds: float) -> None:
|
|
191
|
+
if isinstance(seconds, bool) or not math.isfinite(seconds) or seconds <= 0:
|
|
192
|
+
raise ValueError("MCP response timeout must be finite and greater than zero")
|
|
193
|
+
|
|
194
|
+
|
|
50
195
|
@dataclass(frozen=True)
|
|
51
196
|
class _CallToolCommand:
|
|
52
197
|
name: str
|
|
@@ -101,6 +246,7 @@ class MCPConnectionClient:
|
|
|
101
246
|
read_timeout_seconds: float,
|
|
102
247
|
read_concurrency: int,
|
|
103
248
|
) -> None:
|
|
249
|
+
_validate_response_timeout(read_timeout_seconds)
|
|
104
250
|
self.name = name
|
|
105
251
|
self.display_name = display_name
|
|
106
252
|
self.url = url
|
|
@@ -115,6 +261,7 @@ class MCPConnectionClient:
|
|
|
115
261
|
# The results of the calls already handed to the session, so that a failed connection
|
|
116
262
|
# ends them as well as the calls still queued.
|
|
117
263
|
self._in_flight: set[asyncio.Future[Any]] = set()
|
|
264
|
+
self._http_calls: dict[types.RequestId, _ToolCall] = {}
|
|
118
265
|
self.tools: tuple[types.Tool, ...] = ()
|
|
119
266
|
self.resources: tuple[types.Resource, ...] = ()
|
|
120
267
|
self._parallel_tool_names: frozenset[str] = frozenset()
|
|
@@ -156,15 +303,21 @@ class MCPConnectionClient:
|
|
|
156
303
|
async with httpx.AsyncClient(
|
|
157
304
|
timeout=self._http_timeout,
|
|
158
305
|
auth=self._http_auth,
|
|
306
|
+
event_hooks={"request": [self._prepare_tool_request]},
|
|
159
307
|
) as http_client:
|
|
160
308
|
async with streamable_http_client(
|
|
161
309
|
self.url,
|
|
162
310
|
http_client=http_client,
|
|
311
|
+
calls=self._http_calls,
|
|
163
312
|
terminate_on_close=False,
|
|
164
313
|
) as (read_stream, write_stream, _get_session_id):
|
|
165
314
|
async with ClientSession(
|
|
166
315
|
read_stream,
|
|
167
|
-
|
|
316
|
+
# MCP annotates this as a memory stream, but uses only send/aclose.
|
|
317
|
+
cast(
|
|
318
|
+
MemoryObjectSendStream[SessionMessage],
|
|
319
|
+
_ToolCallWriteStream(write_stream, self._http_calls),
|
|
320
|
+
),
|
|
168
321
|
read_timeout_seconds=timedelta(seconds=self._read_timeout_seconds),
|
|
169
322
|
) as session:
|
|
170
323
|
with bound_contextvars(**initial_log_context):
|
|
@@ -194,6 +347,20 @@ class MCPConnectionClient:
|
|
|
194
347
|
self._fail_pending_commands(
|
|
195
348
|
failure or RuntimeError(f"{self.display_name} MCP client closed")
|
|
196
349
|
)
|
|
350
|
+
self._http_calls.clear()
|
|
351
|
+
|
|
352
|
+
async def _prepare_tool_request(self, request: httpx.Request) -> None:
|
|
353
|
+
if request.method != "POST":
|
|
354
|
+
return
|
|
355
|
+
message = types.JSONRPCMessage.model_validate_json(request.content).root
|
|
356
|
+
if (
|
|
357
|
+
isinstance(message, types.JSONRPCRequest)
|
|
358
|
+
and message.method == "tools/call"
|
|
359
|
+
and message.id in self._http_calls
|
|
360
|
+
):
|
|
361
|
+
# Only this response read follows the finite call deadline. Keep the request's
|
|
362
|
+
# connect/write/pool limits, without mutating the shared HTTP client defaults.
|
|
363
|
+
request.extensions["timeout"] = {**request.extensions["timeout"], "read": None}
|
|
197
364
|
|
|
198
365
|
async def _read_server_catalog(
|
|
199
366
|
self,
|
|
@@ -219,25 +386,38 @@ class MCPConnectionClient:
|
|
|
219
386
|
read_slots = asyncio.Semaphore(self._read_concurrency)
|
|
220
387
|
|
|
221
388
|
async def execute(command: _CallToolCommand | _ReadResourceCommand) -> None:
|
|
389
|
+
if command.result.cancelled():
|
|
390
|
+
return
|
|
222
391
|
self._in_flight.add(command.result)
|
|
223
392
|
try:
|
|
224
393
|
with bound_contextvars(**command.log_context):
|
|
225
394
|
if isinstance(command, _CallToolCommand):
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
395
|
+
with anyio.CancelScope() as cancel_scope:
|
|
396
|
+
call = _ToolCall(cancel_scope)
|
|
397
|
+
token = _tool_call.set(call)
|
|
398
|
+
command.result.add_done_callback(call.cancel_if_abandoned)
|
|
399
|
+
try:
|
|
400
|
+
if command.timeout_seconds is None:
|
|
401
|
+
tool_result = await session.call_tool(
|
|
402
|
+
command.name,
|
|
403
|
+
command.arguments,
|
|
404
|
+
meta=command.meta,
|
|
405
|
+
)
|
|
406
|
+
else:
|
|
407
|
+
tool_result = await session.call_tool(
|
|
408
|
+
command.name,
|
|
409
|
+
command.arguments,
|
|
410
|
+
read_timeout_seconds=timedelta(
|
|
411
|
+
seconds=command.timeout_seconds
|
|
412
|
+
),
|
|
413
|
+
meta=command.meta,
|
|
414
|
+
)
|
|
415
|
+
if not command.result.done():
|
|
416
|
+
command.result.set_result(tool_result)
|
|
417
|
+
finally:
|
|
418
|
+
command.result.remove_done_callback(call.cancel_if_abandoned)
|
|
419
|
+
_tool_call.reset(token)
|
|
420
|
+
call.finish()
|
|
241
421
|
else:
|
|
242
422
|
resource_result = await session.read_resource(AnyUrl(command.uri))
|
|
243
423
|
if not command.result.done():
|
|
@@ -313,8 +493,16 @@ class MCPConnectionClient:
|
|
|
313
493
|
meta: dict[str, object] | None = None,
|
|
314
494
|
timeout_seconds: float | None = None,
|
|
315
495
|
) -> types.CallToolResult:
|
|
316
|
-
"""
|
|
496
|
+
"""Wait for one tool's answer within its finite, positive per-call response deadline.
|
|
497
|
+
|
|
498
|
+
The deadline starts when the queued call reaches the MCP session and replaces the
|
|
499
|
+
session's default response wait. HTTP read inactivity does not shorten it; connect,
|
|
500
|
+
write and pool limits still apply. Timeout or cancellation never replays the call and
|
|
501
|
+
does not establish whether its remote work completed.
|
|
502
|
+
"""
|
|
317
503
|
|
|
504
|
+
if timeout_seconds is not None:
|
|
505
|
+
_validate_response_timeout(timeout_seconds)
|
|
318
506
|
commands = self._require_commands()
|
|
319
507
|
result: asyncio.Future[types.CallToolResult] = asyncio.get_running_loop().create_future()
|
|
320
508
|
commands.put_nowait(
|
|
@@ -45,6 +45,22 @@ class BackendTimeoutError(BackendError):
|
|
|
45
45
|
self.timeout_seconds = timeout_seconds
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
class ApplicationWakingError(BackendError):
|
|
49
|
+
"""The platform accepted access resolution but the application is still starting."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, *, retry_after_seconds: float) -> None:
|
|
52
|
+
super().__init__("Application is waking", detail={"runtime_access_state": "waking"})
|
|
53
|
+
self.retry_after_seconds = retry_after_seconds
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ApplicationReadinessTimeoutError(BackendError):
|
|
57
|
+
"""An application's bounded readiness wait ended before access became ready."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, *, timeout_seconds: float) -> None:
|
|
60
|
+
super().__init__("Application did not become ready")
|
|
61
|
+
self.timeout_seconds = timeout_seconds
|
|
62
|
+
|
|
63
|
+
|
|
48
64
|
class LeaseLostError(TauSDKError):
|
|
49
65
|
code = "runtime_lease_lost"
|
|
50
66
|
status_code = 409
|
|
@@ -16,6 +16,7 @@ credential or an application token to the tool, and a turn's delegation ends wit
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
import asyncio
|
|
19
|
+
import math
|
|
19
20
|
import time
|
|
20
21
|
import uuid
|
|
21
22
|
from collections.abc import Awaitable, Callable, Mapping
|
|
@@ -30,7 +31,13 @@ import structlog
|
|
|
30
31
|
from ms_tau_sdk.backend.auth import AccessToken, _jwt_expiry
|
|
31
32
|
from ms_tau_sdk.backend.client import LeaseProof, answer_without_request_credentials
|
|
32
33
|
from ms_tau_sdk.backend.models import ReleaseRuntimeAccess
|
|
33
|
-
from ms_tau_sdk.errors import
|
|
34
|
+
from ms_tau_sdk.errors import (
|
|
35
|
+
ApplicationReadinessTimeoutError,
|
|
36
|
+
ApplicationWakingError,
|
|
37
|
+
BackendError,
|
|
38
|
+
BackendTimeoutError,
|
|
39
|
+
TauSDKError,
|
|
40
|
+
)
|
|
34
41
|
|
|
35
42
|
logger = structlog.get_logger(__name__)
|
|
36
43
|
|
|
@@ -569,7 +576,12 @@ def _current_turn_binding() -> TurnRequesterBinding | None:
|
|
|
569
576
|
return _TURN_BINDING.get()
|
|
570
577
|
|
|
571
578
|
|
|
572
|
-
async def _turn_application_access(
|
|
579
|
+
async def _turn_application_access(
|
|
580
|
+
release_uid: str,
|
|
581
|
+
*,
|
|
582
|
+
ready_timeout_seconds: float = 120,
|
|
583
|
+
cancelled: Callable[[], bool] | None = None,
|
|
584
|
+
) -> _TurnApplicationAccess:
|
|
573
585
|
"""SDK-private: the current turn's access to one application.
|
|
574
586
|
|
|
575
587
|
The token acts for the person the turn serves, when it serves one, and is the Agent's own
|
|
@@ -579,12 +591,75 @@ async def _turn_application_access(release_uid: str) -> _TurnApplicationAccess:
|
|
|
579
591
|
binding = _TURN_BINDING.get()
|
|
580
592
|
if binding is None:
|
|
581
593
|
raise RuntimeError("Application tools run only inside a turn")
|
|
594
|
+
if not binding.active:
|
|
595
|
+
raise RequesterBindingError("The turn ended while obtaining application access.")
|
|
582
596
|
uid = _canonical_uid(release_uid)
|
|
583
597
|
delegate = binding.serving
|
|
584
|
-
|
|
598
|
+
|
|
599
|
+
async def resolve(*, refresh: bool) -> _ReleaseAccess:
|
|
600
|
+
# Capture delegation once, including for renewal; an ended turn never falls back to the
|
|
601
|
+
# Agent's own access. Each poll reacquires the binding lock and current lease proof.
|
|
602
|
+
started = asyncio.get_running_loop().time()
|
|
603
|
+
deadline = asyncio.timeout(None)
|
|
604
|
+
waiting = False
|
|
605
|
+
try:
|
|
606
|
+
async with deadline:
|
|
607
|
+
while True:
|
|
608
|
+
if not binding.active:
|
|
609
|
+
raise RequesterBindingError(
|
|
610
|
+
"The turn ended while obtaining application access."
|
|
611
|
+
)
|
|
612
|
+
try:
|
|
613
|
+
access = await binding._release_access_for(
|
|
614
|
+
uid, refresh=refresh, delegate=delegate
|
|
615
|
+
)
|
|
616
|
+
except ApplicationWakingError as error:
|
|
617
|
+
if not waiting:
|
|
618
|
+
waiting = True
|
|
619
|
+
deadline.reschedule(started + ready_timeout_seconds)
|
|
620
|
+
logger.info(
|
|
621
|
+
"runtime.application_access.waiting",
|
|
622
|
+
resource_release_uid=uid,
|
|
623
|
+
runtime_access_state="waking",
|
|
624
|
+
retry_after_seconds=error.retry_after_seconds,
|
|
625
|
+
)
|
|
626
|
+
await asyncio.sleep(error.retry_after_seconds)
|
|
627
|
+
else:
|
|
628
|
+
if waiting:
|
|
629
|
+
logger.info(
|
|
630
|
+
"runtime.application_access.ready", resource_release_uid=uid
|
|
631
|
+
)
|
|
632
|
+
return access
|
|
633
|
+
except TimeoutError:
|
|
634
|
+
if not deadline.expired():
|
|
635
|
+
raise
|
|
636
|
+
# No transport error or credential-bearing exception is retained as the cause.
|
|
637
|
+
raise ApplicationReadinessTimeoutError(timeout_seconds=ready_timeout_seconds)
|
|
638
|
+
|
|
639
|
+
async def obtain(*, refresh: bool) -> _ReleaseAccess:
|
|
640
|
+
# ToolCancellationToken exposes only is_cancelled(). Poll it while both the readiness
|
|
641
|
+
# delay and the platform request are pending, and join cancelled work before returning.
|
|
642
|
+
task = asyncio.create_task(resolve(refresh=refresh))
|
|
643
|
+
try:
|
|
644
|
+
while True:
|
|
645
|
+
if not binding.active:
|
|
646
|
+
raise RequesterBindingError(
|
|
647
|
+
"The turn ended while obtaining application access."
|
|
648
|
+
)
|
|
649
|
+
if cancelled is not None and cancelled():
|
|
650
|
+
raise asyncio.CancelledError
|
|
651
|
+
if task.done():
|
|
652
|
+
return task.result()
|
|
653
|
+
await asyncio.wait({task}, timeout=0.1)
|
|
654
|
+
finally:
|
|
655
|
+
if not task.done():
|
|
656
|
+
task.cancel()
|
|
657
|
+
await asyncio.gather(task, return_exceptions=True)
|
|
658
|
+
|
|
659
|
+
access = await obtain(refresh=False)
|
|
585
660
|
|
|
586
661
|
async def renew() -> str:
|
|
587
|
-
return (await
|
|
662
|
+
return (await obtain(refresh=True)).token.value
|
|
588
663
|
|
|
589
664
|
return _TurnApplicationAccess(rpc_url=access.rpc_url, token=access.token.value, renew=renew)
|
|
590
665
|
|
|
@@ -660,6 +735,17 @@ def _token_access(answer: ReleaseRuntimeAccess) -> _ReleaseAccess:
|
|
|
660
735
|
grant = answer.access
|
|
661
736
|
if grant is None:
|
|
662
737
|
state = answer.runtime_access.get("state")
|
|
738
|
+
if state == "waking":
|
|
739
|
+
retry_ms = answer.runtime_access.get("retry_after_ms")
|
|
740
|
+
delay = 2.0
|
|
741
|
+
if isinstance(retry_ms, int | float) and not isinstance(retry_ms, bool):
|
|
742
|
+
try:
|
|
743
|
+
seconds = float(retry_ms) / 1000
|
|
744
|
+
except OverflowError:
|
|
745
|
+
seconds = float("inf")
|
|
746
|
+
if math.isfinite(seconds) and seconds >= 0:
|
|
747
|
+
delay = max(seconds, 0.1)
|
|
748
|
+
raise ApplicationWakingError(retry_after_seconds=delay)
|
|
663
749
|
raise BackendError(
|
|
664
750
|
f"Release {answer.resource_release_uid} is not ready to be called"
|
|
665
751
|
+ (f" (runtime access: {state})" if isinstance(state, str) else ""),
|
|
@@ -220,6 +220,7 @@ class TauSDKSettings(BaseSettings):
|
|
|
220
220
|
backend_read_timeout_seconds: float = Field(
|
|
221
221
|
default=60.0,
|
|
222
222
|
gt=0,
|
|
223
|
+
allow_inf_nan=False,
|
|
223
224
|
validation_alias="MAINSEQUENCE_TAU_BACKEND_READ_TIMEOUT_SECONDS",
|
|
224
225
|
)
|
|
225
226
|
backend_write_timeout_seconds: float = Field(
|
|
@@ -248,6 +249,12 @@ class TauSDKSettings(BaseSettings):
|
|
|
248
249
|
gt=0,
|
|
249
250
|
validation_alias="TAU_MCP_TOOL_TIMEOUT_SECONDS",
|
|
250
251
|
)
|
|
252
|
+
mcp_application_ready_timeout_seconds: float = Field(
|
|
253
|
+
default=120.0,
|
|
254
|
+
gt=0,
|
|
255
|
+
allow_inf_nan=False,
|
|
256
|
+
validation_alias="TAU_MCP_APPLICATION_READY_TIMEOUT_SECONDS",
|
|
257
|
+
)
|
|
251
258
|
backend_max_response_bytes: int = Field(
|
|
252
259
|
default=10 * 1024 * 1024,
|
|
253
260
|
gt=0,
|
{ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/tools/mcp_applications.py
RENAMED
|
@@ -19,7 +19,8 @@ from __future__ import annotations
|
|
|
19
19
|
import json
|
|
20
20
|
import math
|
|
21
21
|
import re
|
|
22
|
-
from collections.abc import AsyncGenerator,
|
|
22
|
+
from collections.abc import AsyncGenerator, Mapping, Sequence
|
|
23
|
+
from typing import Protocol
|
|
23
24
|
|
|
24
25
|
import httpx
|
|
25
26
|
import structlog
|
|
@@ -35,7 +36,13 @@ from tau_agent.types import JSONValue
|
|
|
35
36
|
|
|
36
37
|
from ms_tau_sdk.backend.mcp import MCPConnectionClient
|
|
37
38
|
from ms_tau_sdk.backend.models import MCPApplication
|
|
38
|
-
from ms_tau_sdk.errors import
|
|
39
|
+
from ms_tau_sdk.errors import (
|
|
40
|
+
ApplicationReadinessTimeoutError,
|
|
41
|
+
ApplicationWakingError,
|
|
42
|
+
BackendError,
|
|
43
|
+
BackendTimeoutError,
|
|
44
|
+
ConfigurationError,
|
|
45
|
+
)
|
|
39
46
|
from ms_tau_sdk.runtime.requester import (
|
|
40
47
|
RequesterBindingError,
|
|
41
48
|
_turn_application_access,
|
|
@@ -52,7 +59,11 @@ _RESERVED_NAMES = frozenset({"mainsequence"})
|
|
|
52
59
|
TIMEOUT_META_KEY = "mainsequence.ai/timeout-seconds/v1"
|
|
53
60
|
"""The ``_meta`` key under which an application tool declares how long it may run, in seconds."""
|
|
54
61
|
|
|
55
|
-
|
|
62
|
+
|
|
63
|
+
class ApplicationConnector(Protocol):
|
|
64
|
+
async def __call__(
|
|
65
|
+
self, application: MCPApplication, *, signal: ToolCancellationToken | None = None
|
|
66
|
+
) -> MCPConnectionClient: ...
|
|
56
67
|
|
|
57
68
|
|
|
58
69
|
class _ApplicationTokenAuth(httpx.Auth):
|
|
@@ -77,18 +88,24 @@ class _ApplicationTokenAuth(httpx.Auth):
|
|
|
77
88
|
def application_connector(settings: TauSDKSettings) -> ApplicationConnector:
|
|
78
89
|
"""Open an MCP session to one application for the current turn."""
|
|
79
90
|
|
|
80
|
-
async def connect(
|
|
81
|
-
|
|
91
|
+
async def connect(
|
|
92
|
+
application: MCPApplication, *, signal: ToolCancellationToken | None = None
|
|
93
|
+
) -> MCPConnectionClient:
|
|
94
|
+
access = await _turn_application_access(
|
|
95
|
+
application.resource_release_uid,
|
|
96
|
+
ready_timeout_seconds=settings.mcp_application_ready_timeout_seconds,
|
|
97
|
+
cancelled=signal.is_cancelled if signal is not None else None,
|
|
98
|
+
)
|
|
82
99
|
return await MCPConnectionClient(
|
|
83
100
|
name=application.name,
|
|
84
101
|
display_name=application.name,
|
|
85
102
|
url=f"{str(access.rpc_url).rstrip('/')}/mcp",
|
|
86
103
|
auth=_ApplicationTokenAuth(access),
|
|
87
|
-
# The
|
|
88
|
-
#
|
|
104
|
+
# The connection applies the tool's response deadline to its call; these HTTP limits
|
|
105
|
+
# also bound initialization and catalog requests.
|
|
89
106
|
timeout=httpx.Timeout(
|
|
90
107
|
connect=settings.backend_connect_timeout_seconds,
|
|
91
|
-
read=settings.
|
|
108
|
+
read=settings.backend_read_timeout_seconds,
|
|
92
109
|
write=settings.backend_write_timeout_seconds,
|
|
93
110
|
pool=settings.backend_pool_timeout_seconds,
|
|
94
111
|
),
|
|
@@ -118,6 +135,23 @@ def _failure(
|
|
|
118
135
|
|
|
119
136
|
if isinstance(error, RequesterBindingError):
|
|
120
137
|
return "Your access for this request ended.", {"failure": "access_ended"}
|
|
138
|
+
if isinstance(error, ApplicationReadinessTimeoutError):
|
|
139
|
+
return (
|
|
140
|
+
f"The {name} application did not become ready within "
|
|
141
|
+
f"{error.timeout_seconds:g} seconds. "
|
|
142
|
+
"It is still starting; try again shortly.",
|
|
143
|
+
{
|
|
144
|
+
"failure": "readiness_timeout",
|
|
145
|
+
"phase": "readiness",
|
|
146
|
+
"runtime_access_state": "waking",
|
|
147
|
+
"timeout_seconds": error.timeout_seconds,
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
if isinstance(error, ApplicationWakingError):
|
|
151
|
+
return (
|
|
152
|
+
f"The {name} application is still starting; try again shortly.",
|
|
153
|
+
{"failure": "not_ready", "phase": "readiness", "runtime_access_state": "waking"},
|
|
154
|
+
)
|
|
121
155
|
if isinstance(error, BackendError):
|
|
122
156
|
if isinstance(error, BackendTimeoutError) or error.backend_status in {408, 504}:
|
|
123
157
|
details: dict[str, JSONValue] = {"failure": "timeout", "phase": "access"}
|
|
@@ -217,7 +251,7 @@ def _application_tools(
|
|
|
217
251
|
async def open_session(signal: ToolCancellationToken | None) -> MCPConnectionClient:
|
|
218
252
|
# Nothing has reached a tool while the session opens, so one more attempt is safe.
|
|
219
253
|
try:
|
|
220
|
-
return await connect(application)
|
|
254
|
+
return await connect(application, signal=signal)
|
|
221
255
|
except Exception as error:
|
|
222
256
|
if not _retryable(error) or (signal is not None and signal.is_cancelled()):
|
|
223
257
|
raise
|
|
@@ -226,7 +260,7 @@ def _application_tools(
|
|
|
226
260
|
application=name,
|
|
227
261
|
error_type=type(error).__name__,
|
|
228
262
|
)
|
|
229
|
-
return await connect(application)
|
|
263
|
+
return await connect(application, signal=signal)
|
|
230
264
|
|
|
231
265
|
def failed(
|
|
232
266
|
operation: str,
|
|
File without changes
|
|
File without changes
|
{ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/__init__.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/app.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/cli.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/config.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/env_file.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/logs.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/proxy.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/packages/tau-board/src/ms_tau_board/state.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/protocols/assistant_ui.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/protocols/chat_history.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/runtime/deployment_health.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ms_tau_sdk-2.0.10.dev50 → ms_tau_sdk-2.0.11.dev52}/src/ms_tau_sdk/tools/mainsequence_mcp.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|