ms-tau-sdk 2.0.2.dev34__tar.gz → 2.0.3.dev35__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 (84) hide show
  1. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/CHANGELOG.md +12 -0
  2. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/PKG-INFO +1 -1
  3. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/pyproject.toml +1 -1
  4. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/manager.py +8 -1
  5. ms_tau_sdk-2.0.3.dev35/src/ms_tau_sdk/runtime/secret_entry.py +74 -0
  6. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/session.py +15 -0
  7. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/tools/mainsequence_mcp.py +80 -10
  8. ms_tau_sdk-2.0.3.dev35/src/ms_tau_sdk/tools/secret_entry.py +116 -0
  9. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/.gitignore +0 -0
  10. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/README.md +0 -0
  11. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/__init__.py +0 -0
  12. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/app.py +0 -0
  13. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/cli.py +0 -0
  14. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/config.py +0 -0
  15. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/env_file.py +0 -0
  16. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/logs.py +0 -0
  17. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/proxy.py +0 -0
  18. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/state.py +0 -0
  19. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/static/BULMA-LICENSE.txt +0 -0
  20. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/static/app.js +0 -0
  21. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/static/board.css +0 -0
  22. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/static/bulma.min.css +0 -0
  23. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/static/index.html +0 -0
  24. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/__init__.py +0 -0
  25. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +0 -0
  26. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +0 -0
  27. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +0 -0
  28. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +0 -0
  29. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/__init__.py +0 -0
  30. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/a2a.py +0 -0
  31. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/chat.py +0 -0
  32. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/conversations.py +0 -0
  33. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/dependencies.py +0 -0
  34. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/health.py +0 -0
  35. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/inspection.py +0 -0
  36. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/local_chat.py +0 -0
  37. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/models.py +0 -0
  38. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/request_identity.py +0 -0
  39. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/sessions.py +0 -0
  40. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/app.py +0 -0
  41. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/application.py +0 -0
  42. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/__init__.py +0 -0
  43. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/assertions.py +0 -0
  44. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/auth.py +0 -0
  45. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/client.py +0 -0
  46. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/local.py +0 -0
  47. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/mcp.py +0 -0
  48. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/models.py +0 -0
  49. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/routes.py +0 -0
  50. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/cli.py +0 -0
  51. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/errors.py +0 -0
  52. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/logging.py +0 -0
  53. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/__init__.py +0 -0
  54. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/a2a_failure.py +0 -0
  55. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
  56. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
  57. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/assistant_ui.py +0 -0
  58. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/chat_history.py +0 -0
  59. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
  60. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/providers/__init__.py +0 -0
  61. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/providers/definitions.py +0 -0
  62. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/providers/factory.py +0 -0
  63. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/providers/tau_compat.py +0 -0
  64. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
  65. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/resources/__init__.py +0 -0
  66. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/resources/loader.py +0 -0
  67. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
  68. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/__init__.py +0 -0
  69. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
  70. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/events.py +0 -0
  71. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/extensions.py +0 -0
  72. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/failures.py +0 -0
  73. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/live_turns.py +0 -0
  74. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/observability.py +0 -0
  75. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/provenance.py +0 -0
  76. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
  77. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/task_context.py +0 -0
  78. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/sessions/__init__.py +0 -0
  79. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/sessions/storage.py +0 -0
  80. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/settings.py +0 -0
  81. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/skills.py +0 -0
  82. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/tools/__init__.py +0 -0
  83. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/tools/skill_read.py +0 -0
  84. {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/tools/task_control.py +0 -0
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ - Main Sequence MCP no longer offers `agent_session.resolve_runtime_access` to the model. Its
6
+ result carries a short-lived runtime token for direct-runtime clients, and Tau's generic MCP
7
+ projection copied that token into model-visible tool content and into tool details, which reach
8
+ stream events and session history. Tau never needs the token: agent-to-agent turns use
9
+ `a2a.send_message`. The operation is left out of the session's tool list, so
10
+ `mainsequence__agent_session_resolve_runtime_access` is no longer a tool; every other MCP tool
11
+ and its result are unchanged. Direct-runtime clients that call the operation themselves are
12
+ unaffected. See the 2026-10-06 amendment of ADR 0002
13
+ ([issue #65](https://github.com/mainsequence-sdk/ms-tau-sdk/issues/65)).
14
+
3
15
  ## 2.0.2 — 2026-10-06
4
16
 
5
17
  - Runtime credentials require the projected workload identity token file. The runtime credential
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ms-tau-sdk
3
- Version: 2.0.2.dev34
3
+ Version: 2.0.3.dev35
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ms-tau-sdk"
7
- version = "2.0.2.dev34"
7
+ version = "2.0.3.dev35"
8
8
  description = "Workspace-bound Tau application primitives for Main Sequence projects"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -1116,13 +1116,17 @@ class SessionRuntimeManager:
1116
1116
  )
1117
1117
  cwd = self._resolve_cwd()
1118
1118
  project_extension_state = ProjectExtensionState(enabled=True)
1119
- caller_session_proof = None
1119
+ caller_session_proof: dict[str, JSONValue] | None = None
1120
1120
  if not self.settings.local_mode:
1121
1121
  caller_session_proof = {
1122
1122
  "caller_agent_session_uid": session_uid,
1123
1123
  "lease_holder_id": lease.holder_id,
1124
1124
  "lease_token": lease.lease_token,
1125
1125
  }
1126
+
1127
+ async def record_secret_entry(payload: dict[str, JSONValue]) -> None:
1128
+ await coding_session.append_custom_entry("mainsequence.secret_entry", payload)
1129
+
1126
1130
  tools = []
1127
1131
  if self.settings.exclude_base_tools:
1128
1132
  # Tau lists skills only when a tool named `read` exists. The tool runs only in
@@ -1134,6 +1138,8 @@ class SessionRuntimeManager:
1134
1138
  tools.extend(
1135
1139
  create_mainsequence_mcp_tools(
1136
1140
  mcp_client,
1141
+ session_uid=session_uid,
1142
+ on_secret_entry=record_secret_entry,
1137
1143
  caller_session_proof=caller_session_proof,
1138
1144
  allow_missing_session_proof=self.settings.local_mode,
1139
1145
  )
@@ -1202,6 +1208,7 @@ class SessionRuntimeManager:
1202
1208
  provider_name=provider_runtime.name,
1203
1209
  model=provider_runtime.model,
1204
1210
  mcp_client=mcp_client,
1211
+ secret_entry_proof=caller_session_proof,
1205
1212
  runtime_config_sha256=str(session_extra.get("runtime_config_sha256", "")),
1206
1213
  provider_control_schema=bootstrap.provider_control.schema_version,
1207
1214
  catalog_digest=bootstrap.provider_control.catalog_digest,
@@ -0,0 +1,74 @@
1
+ """Refresh persisted, reference-only Secret requests before a resumed conversation turn."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Awaitable, Callable, Sequence
7
+
8
+ from tau_agent.session import CustomEntry, SessionEntry
9
+ from tau_agent.types import JSONValue
10
+
11
+ from ms_tau_sdk.backend.mcp import MainSequenceMCPClient
12
+ from ms_tau_sdk.tools.secret_entry import SecretEntryStatus, secret_entry_result
13
+
14
+
15
+ async def refresh_secret_entries(
16
+ *,
17
+ entries: Sequence[SessionEntry],
18
+ client: MainSequenceMCPClient,
19
+ private_proof: dict[str, JSONValue] | None,
20
+ record: Callable[[dict[str, JSONValue]], Awaitable[None]],
21
+ ) -> str:
22
+ latest: dict[str, SecretEntryStatus] = {}
23
+ for entry in entries:
24
+ if isinstance(entry, CustomEntry) and entry.namespace == "mainsequence.secret_entry":
25
+ # Stored state is not proof of completion. Revalidate pending references below.
26
+ try:
27
+ status = SecretEntryStatus.model_validate_json(json.dumps(entry.data))
28
+ except ValueError:
29
+ raise RuntimeError(
30
+ "Stored Secret entry status is invalid. Check the request before continuing."
31
+ ) from None
32
+ latest[str(status.uid)] = status
33
+ checks = []
34
+ for status in latest.values():
35
+ if status.status not in {"pending", "submitting"}:
36
+ continue
37
+ try:
38
+ arguments: dict[str, object] = {
39
+ "request_uid": str(status.uid),
40
+ "organization_environment_uid": str(status.organization_environment_uid),
41
+ }
42
+ result = await client.call_tool(
43
+ "secret_entry.status",
44
+ arguments,
45
+ meta={"mainsequence.ai/caller-session-proof/v1": private_proof}
46
+ if private_proof is not None
47
+ else None,
48
+ )
49
+ projected = secret_entry_result("secret_entry.status", result)
50
+ if not isinstance(projected.details, dict):
51
+ raise ValueError("Unverified status")
52
+ payload = projected.details.get("structured_content")
53
+ if projected.details.get("is_error") or not isinstance(payload, dict):
54
+ raise ValueError("Unverified status")
55
+ if payload.get("uid") != str(status.uid) or payload.get(
56
+ "organization_environment_uid"
57
+ ) != str(status.organization_environment_uid):
58
+ raise ValueError("Unbound status")
59
+ except Exception:
60
+ # Do not run the model with an assumed success or copy transport errors to history.
61
+ raise RuntimeError(
62
+ "Secret entry status could not be verified. Retry the conversation after "
63
+ "checking the private page; never send a value in chat."
64
+ ) from None
65
+ await record(payload)
66
+ checks.append(projected.text)
67
+ if not checks:
68
+ return ""
69
+ return (
70
+ "\n\nPlatform Secret-entry status checked by the host before this turn:\n"
71
+ + "\n".join(checks)
72
+ + "\nContinue dependent work only for confirmed completion. Do not repeat an already "
73
+ "completed business operation or request the secret value."
74
+ )
@@ -16,6 +16,7 @@ from ms_tau_sdk.backend.models import TauTurnCommit
16
16
  from ms_tau_sdk.runtime.events import TauRuntimeEvent, translate_tau_event
17
17
  from ms_tau_sdk.runtime.extensions import ProjectExtensionState
18
18
  from ms_tau_sdk.runtime.provenance import PROVENANCE_NAMESPACE, TurnProvenance
19
+ from ms_tau_sdk.runtime.secret_entry import refresh_secret_entries
19
20
  from ms_tau_sdk.sessions.storage import BackendSessionStorage
20
21
 
21
22
  type PlatformEvent = tuple[str, dict[str, JSONValue]]
@@ -31,6 +32,7 @@ class ActiveSessionRuntime:
31
32
  provider_name: str = ""
32
33
  model: str = ""
33
34
  mcp_client: MainSequenceMCPClient | None = None
35
+ secret_entry_proof: dict[str, JSONValue] | None = field(default=None, repr=False)
34
36
  lease_renew_task: asyncio.Task[None] | None = None
35
37
  persistence_task: asyncio.Task[object] | None = None
36
38
  pending_snapshot_commit: TauTurnCommit | None = None
@@ -91,6 +93,19 @@ class ActiveSessionRuntime:
91
93
  if platform_event is not None:
92
94
  namespace, payload = platform_event
93
95
  await self.coding_session.append_custom_entry(namespace, dict(payload))
96
+ if self.mcp_client is not None:
97
+
98
+ async def record_secret_entry(payload: dict[str, JSONValue]) -> None:
99
+ await self.coding_session.append_custom_entry(
100
+ "mainsequence.secret_entry", payload
101
+ )
102
+
103
+ content += await refresh_secret_entries(
104
+ entries=await self.storage.read_all(),
105
+ client=self.mcp_client,
106
+ private_proof=self.secret_entry_proof,
107
+ record=record_secret_entry,
108
+ )
94
109
  async for event in self.coding_session.prompt(content):
95
110
  translated = translate_tau_event(event)
96
111
  if settled_event is not None and translated.type != "agent_settled":
@@ -4,9 +4,10 @@ from __future__ import annotations
4
4
 
5
5
  import json
6
6
  import re
7
- from collections.abc import Mapping
7
+ from collections.abc import Awaitable, Callable, Mapping
8
8
  from copy import deepcopy
9
9
  from typing import Literal, cast
10
+ from uuid import NAMESPACE_URL, uuid5
10
11
 
11
12
  from mcp import types
12
13
  from tau_agent.messages import ImageContent, TextContent
@@ -23,12 +24,17 @@ from ms_tau_sdk.backend.mcp import (
23
24
  ENVIRONMENT_UID_ARGUMENT,
24
25
  MainSequenceMCPClient,
25
26
  )
27
+ from ms_tau_sdk.tools.secret_entry import secret_entry_result
26
28
 
27
29
  _INVALID_TOOL_NAME = re.compile(r"[^A-Za-z0-9_-]")
28
30
  _RESOURCE_TOOL_NAME = "mainsequence__read_resource"
29
31
  CALLER_SESSION_PROOF_META_KEY = "mainsequence.ai/caller-session-proof/v1"
30
32
  CALLER_SESSION_PROOF_REQUIRED_META_KEY = "mainsequence.ai/requires-caller-session-proof/v1"
31
33
  A2A_SEND_TOOL = "a2a.send_message"
34
+ # These operations return credentials for direct-runtime clients. Tau never calls them for
35
+ # the model: agent-to-agent turns use a2a.send_message, so the credential stays out of model
36
+ # content, tool details, events, and persisted history.
37
+ CREDENTIAL_RESULT_MCP_TOOLS = frozenset({"agent_session.resolve_runtime_access"})
32
38
 
33
39
 
34
40
  def _tau_tool_name(mcp_name: str) -> str:
@@ -51,6 +57,8 @@ def _tool_result(
51
57
  canonical_name: str,
52
58
  result: types.CallToolResult,
53
59
  ) -> AgentToolResult:
60
+ if canonical_name in {"secret_entry.start", "secret_entry.status", "secret_entry.cancel"}:
61
+ return secret_entry_result(canonical_name, result)
54
62
  content: list[TextContent | ImageContent] = []
55
63
  for block in result.content:
56
64
  if isinstance(block, types.TextContent):
@@ -87,6 +95,11 @@ def _tau_tool_input_schema(
87
95
  local_user_semantics: bool = False,
88
96
  ) -> Mapping[str, JSONValue]:
89
97
  schema = deepcopy(tool.inputSchema)
98
+ if tool.name == "secret_entry.start":
99
+ schema.get("properties", {}).pop("idempotency_key", None)
100
+ schema["required"] = [
101
+ name for name in schema.get("required", []) if name != "idempotency_key"
102
+ ]
90
103
  if tool.name == A2A_SEND_TOOL:
91
104
  properties = schema.setdefault("properties", {})
92
105
  if isinstance(properties, dict):
@@ -160,6 +173,8 @@ def _create_mcp_tool(
160
173
  tau_name: str,
161
174
  private_meta: Mapping[str, JSONValue] | None = None,
162
175
  local_user_semantics: bool = False,
176
+ session_uid: str | None = None,
177
+ on_secret_entry: Callable[[dict[str, JSONValue]], Awaitable[None]] | None = None,
163
178
  ) -> AgentTool:
164
179
  canonical_name = tool.name
165
180
  annotations = tool.annotations
@@ -177,7 +192,36 @@ def _create_mcp_tool(
177
192
  signal: ToolCancellationToken | None = None,
178
193
  on_update: ToolUpdateCallback | None = None,
179
194
  ) -> AgentToolResult:
180
- del tool_call_id, on_update
195
+ del on_update
196
+ if canonical_name in {"secret_entry.start", "secret_entry.status", "secret_entry.cancel"}:
197
+ allowed = {"organization_environment_uid", "request_uid"}
198
+ if canonical_name == "secret_entry.start":
199
+ allowed = {
200
+ "organization_environment_uid",
201
+ "name",
202
+ "secret_uid",
203
+ "purpose",
204
+ "consumer_user_uid",
205
+ }
206
+ if set(arguments) - allowed:
207
+ return AgentToolResult(
208
+ content=[
209
+ TextContent(
210
+ text=(
211
+ "Secret-entry tools accept only metadata and references. "
212
+ "Enter values on the private page."
213
+ )
214
+ )
215
+ ],
216
+ details={"mcp_tool": canonical_name, "is_error": True},
217
+ )
218
+ if canonical_name == "secret_entry.start":
219
+ arguments = {
220
+ **arguments,
221
+ "idempotency_key": str(
222
+ uuid5(NAMESPACE_URL, f"ms-tau-secret-entry:{session_uid}:{tool_call_id}")
223
+ ),
224
+ }
181
225
  if signal is not None and signal.is_cancelled():
182
226
  return AgentToolResult(
183
227
  content=[TextContent(text="Main Sequence MCP tool call was cancelled.")],
@@ -199,15 +243,35 @@ def _create_mcp_tool(
199
243
  raise ValueError("Local A2A Task communication requires completion_policy 'poll'")
200
244
  if response_kind == "message" and completion_policy is not None:
201
245
  raise ValueError("completion_policy is not valid for Message response_kind")
202
- if private_meta is None:
203
- result = await client.call_tool(canonical_name, dict(arguments))
204
- else:
205
- result = await client.call_tool(
206
- canonical_name,
207
- dict(arguments),
208
- meta=dict(private_meta),
246
+ try:
247
+ if private_meta is None:
248
+ result = await client.call_tool(canonical_name, dict(arguments))
249
+ else:
250
+ result = await client.call_tool(
251
+ canonical_name,
252
+ dict(arguments),
253
+ meta=dict(private_meta),
254
+ )
255
+ except Exception:
256
+ if canonical_name not in {
257
+ "secret_entry.start",
258
+ "secret_entry.status",
259
+ "secret_entry.cancel",
260
+ }:
261
+ raise
262
+ return secret_entry_result(
263
+ canonical_name, types.CallToolResult(content=[], isError=True)
209
264
  )
210
- return _tool_result(canonical_name=canonical_name, result=result)
265
+ projected = _tool_result(canonical_name=canonical_name, result=result)
266
+ if (
267
+ canonical_name.startswith("secret_entry.")
268
+ and on_secret_entry is not None
269
+ and isinstance(projected.details, dict)
270
+ ):
271
+ payload = projected.details.get("structured_content")
272
+ if not projected.details.get("is_error") and isinstance(payload, dict):
273
+ await on_secret_entry(payload)
274
+ return projected
211
275
 
212
276
  return AgentTool(
213
277
  name=tau_name,
@@ -285,10 +349,14 @@ def create_mainsequence_mcp_tools(
285
349
  *,
286
350
  caller_session_proof: Mapping[str, JSONValue] | None = None,
287
351
  allow_missing_session_proof: bool = False,
352
+ session_uid: str | None = None,
353
+ on_secret_entry: Callable[[dict[str, JSONValue]], Awaitable[None]] | None = None,
288
354
  ) -> list[AgentTool]:
289
355
  tools: list[AgentTool] = []
290
356
  names: set[str] = set()
291
357
  for tool in client.tools:
358
+ if tool.name in CREDENTIAL_RESULT_MCP_TOOLS:
359
+ continue
292
360
  tau_name = _tau_tool_name(tool.name)
293
361
  if tau_name in names or tau_name == _RESOURCE_TOOL_NAME:
294
362
  raise ValueError(f"Main Sequence MCP tool name collision: {tool.name}")
@@ -304,6 +372,8 @@ def create_mainsequence_mcp_tools(
304
372
  allow_missing_proof=allow_missing_session_proof,
305
373
  ),
306
374
  local_user_semantics=allow_missing_session_proof,
375
+ session_uid=session_uid,
376
+ on_secret_entry=on_secret_entry,
307
377
  )
308
378
  )
309
379
  if client.resources:
@@ -0,0 +1,116 @@
1
+ """Reference-only Secret entry. Values travel directly from a private browser to Django."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import datetime
7
+ from typing import Literal
8
+ from urllib.parse import parse_qs, urlsplit
9
+ from uuid import UUID
10
+
11
+ from mcp import types
12
+ from pydantic import BaseModel, ConfigDict, ValidationError, model_validator
13
+ from tau_agent.messages import TextContent
14
+ from tau_agent.tools import AgentToolResult
15
+
16
+ from ms_tau_sdk.runtime.task_context import active_task_execution
17
+
18
+
19
+ class SecretEntryStatus(BaseModel):
20
+ model_config = ConfigDict(extra="forbid", strict=True)
21
+
22
+ uid: UUID
23
+ status: Literal["pending", "submitting", "completed", "cancelled", "expired", "failed"]
24
+ operation: Literal["create", "rotate"]
25
+ name: str
26
+ purpose: str
27
+ organization_environment_uid: UUID
28
+ organization_environment_name: str
29
+ requesting_agent_name: str | None
30
+ consumer_user_uid: UUID | None
31
+ secret_uid: UUID | None
32
+ entry_url: str
33
+ expires_at: datetime
34
+ error_code: str
35
+ requirement_reference: str
36
+
37
+ @model_validator(mode="after")
38
+ def trusted_entry_link(self) -> SecretEntryStatus:
39
+ url = urlsplit(self.entry_url)
40
+ local = url.scheme == "http" and url.hostname in {"localhost", "127.0.0.1"}
41
+ if (
42
+ (url.scheme != "https" and not local)
43
+ or not url.hostname
44
+ or url.username
45
+ or url.password
46
+ or url.fragment
47
+ or url.path != "/app/main-sequence-foundry/secrets"
48
+ or parse_qs(url.query)
49
+ != {
50
+ "secretEntryRequest": [str(self.uid)],
51
+ "organization_environment_uid": [str(self.organization_environment_uid)],
52
+ }
53
+ or self.requirement_reference != f"secret-entry:{self.uid}"
54
+ or (self.status != "completed" and self.secret_uid is not None)
55
+ ):
56
+ raise ValueError("Invalid Secret-entry binding")
57
+ return self
58
+
59
+
60
+ def secret_entry_result(canonical_name: str, result: types.CallToolResult) -> AgentToolResult:
61
+ """Ignore free-form MCP content; accept only the complete value-free status contract."""
62
+ try:
63
+ if result.isError or result.structuredContent is None:
64
+ raise ValueError("Secret entry is unavailable")
65
+ # Strict JSON accepts UUID/date strings but rejects coercion and extra fields.
66
+ status = SecretEntryStatus.model_validate_json(json.dumps(result.structuredContent))
67
+ except (ValidationError, ValueError, TypeError):
68
+ return AgentToolResult(
69
+ content=[
70
+ TextContent(
71
+ text=(
72
+ "Secret entry could not be verified. Check access or retry; "
73
+ "never send a value in chat."
74
+ )
75
+ )
76
+ ],
77
+ details={"mcp_tool": canonical_name, "is_error": True},
78
+ )
79
+ payload = status.model_dump(mode="json")
80
+ if status.status == "pending":
81
+ guidance = (
82
+ f"Enter the value on the authenticated private page: {status.entry_url}\n"
83
+ "Open it yourself outside agent browser control or screen capture. "
84
+ "Do not paste the value here. "
85
+ "Wait for the person, then call secret_entry.status; "
86
+ "opening the page does not complete the request."
87
+ )
88
+ elif status.status == "submitting":
89
+ guidance = (
90
+ "Storage is in progress. Check secret_entry.status; do not repeat the submission."
91
+ )
92
+ elif status.status == "completed":
93
+ guidance = "Django confirmed storage. Saving does not grant access to a consumer."
94
+ elif status.error_code == "storage_outcome_unknown":
95
+ guidance = (
96
+ "The storage outcome is uncertain. Do not retry the value write; "
97
+ "ask an operator to reconcile this request."
98
+ )
99
+ else:
100
+ guidance = f"Secret entry is {status.status}. No completion is confirmed."
101
+ task = active_task_execution()
102
+ if (
103
+ canonical_name == "secret_entry.start"
104
+ and status.status == "pending"
105
+ and task is not None
106
+ and task.interruption_status is None
107
+ ):
108
+ task.request_interruption(
109
+ status="auth_required",
110
+ text=guidance,
111
+ details={"requirementReference": status.requirement_reference},
112
+ )
113
+ return AgentToolResult(
114
+ content=[TextContent(text=guidance), TextContent(text=json.dumps(payload, sort_keys=True))],
115
+ details={"mcp_tool": canonical_name, "is_error": False, "structured_content": payload},
116
+ )