databricks-agentbricks 0.2.0__py3-none-any.whl

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 (159) hide show
  1. databricks_agentbricks/__init__.py +1 -0
  2. databricks_agentbricks/_group.py +217 -0
  3. databricks_agentbricks/agent_project.py +657 -0
  4. databricks_agentbricks/app_resources.py +139 -0
  5. databricks_agentbricks/cli/__init__.py +12 -0
  6. databricks_agentbricks/cli/app.py +108 -0
  7. databricks_agentbricks/cli/app_auth.py +198 -0
  8. databricks_agentbricks/cli/auth.py +142 -0
  9. databricks_agentbricks/cli/deploy.py +954 -0
  10. databricks_agentbricks/cli/dev.py +310 -0
  11. databricks_agentbricks/cli/endpoint.py +145 -0
  12. databricks_agentbricks/cli/endpoint_examples.py +33 -0
  13. databricks_agentbricks/cli/endpoint_output.py +53 -0
  14. databricks_agentbricks/cli/endpoint_request.py +71 -0
  15. databricks_agentbricks/cli/endpoint_transport.py +148 -0
  16. databricks_agentbricks/cli/help.py +467 -0
  17. databricks_agentbricks/cli/init.py +496 -0
  18. databricks_agentbricks/cli/mcp.py +75 -0
  19. databricks_agentbricks/cli/memory.py +520 -0
  20. databricks_agentbricks/cli/sandbox.py +823 -0
  21. databricks_agentbricks/cli/sessions.py +588 -0
  22. databricks_agentbricks/cli/tools.py +456 -0
  23. databricks_agentbricks/cli/tracing.py +724 -0
  24. databricks_agentbricks/databricks_cli.py +38 -0
  25. databricks_agentbricks/errors.py +114 -0
  26. databricks_agentbricks/lakebase_runtime_store.py +107 -0
  27. databricks_agentbricks/legacy_lakebase_runtime_store.py +74 -0
  28. databricks_agentbricks/project_config.py +157 -0
  29. databricks_agentbricks/project_types.py +55 -0
  30. databricks_agentbricks/py.typed +0 -0
  31. databricks_agentbricks/render.py +349 -0
  32. databricks_agentbricks/templates/agent-bricks-migrate/SKILL.md +101 -0
  33. databricks_agentbricks/templates/agent-langgraph/.env.example +36 -0
  34. databricks_agentbricks/templates/agent-langgraph/.gitignore +199 -0
  35. databricks_agentbricks/templates/agent-langgraph/AGENTKIT_CONTRACT.md +95 -0
  36. databricks_agentbricks/templates/agent-langgraph/AGENTS.md +31 -0
  37. databricks_agentbricks/templates/agent-langgraph/CLAUDE.md +1 -0
  38. databricks_agentbricks/templates/agent-langgraph/README.md +141 -0
  39. databricks_agentbricks/templates/agent-langgraph/agent/__init__.py +0 -0
  40. databricks_agentbricks/templates/agent-langgraph/agent/agent.py +177 -0
  41. databricks_agentbricks/templates/agent-langgraph/agent/mcps.py +27 -0
  42. databricks_agentbricks/templates/agent-langgraph/agent/tools/__init__.py +24 -0
  43. databricks_agentbricks/templates/agent-langgraph/agent/tools/sample_tool.py +15 -0
  44. databricks_agentbricks/templates/agent-langgraph/agent/tools/send_message.py +17 -0
  45. databricks_agentbricks/templates/agent-langgraph/app.yaml +15 -0
  46. databricks_agentbricks/templates/agent-langgraph/pyproject.toml +42 -0
  47. databricks_agentbricks/templates/agent-langgraph/runtime/__init__.py +0 -0
  48. databricks_agentbricks/templates/agent-langgraph/runtime/adapter.py +144 -0
  49. databricks_agentbricks/templates/agent-langgraph/runtime/main.py +25 -0
  50. databricks_agentbricks/templates/agent-langgraph/tests/__init__.py +0 -0
  51. databricks_agentbricks/templates/agent-langgraph/tests/test_agent.py +211 -0
  52. databricks_agentbricks/templates/agent-langgraph/tests/test_runtime.py +19 -0
  53. databricks_agentbricks/templates/agent-langgraph/tests/test_workspace.py +28 -0
  54. databricks_agentbricks/templates/agent-openai/.env.example +37 -0
  55. databricks_agentbricks/templates/agent-openai/.gitignore +199 -0
  56. databricks_agentbricks/templates/agent-openai/AGENTKIT_CONTRACT.md +107 -0
  57. databricks_agentbricks/templates/agent-openai/AGENTS.md +76 -0
  58. databricks_agentbricks/templates/agent-openai/CLAUDE.md +1 -0
  59. databricks_agentbricks/templates/agent-openai/README.md +143 -0
  60. databricks_agentbricks/templates/agent-openai/agent/__init__.py +0 -0
  61. databricks_agentbricks/templates/agent-openai/agent/agent.py +205 -0
  62. databricks_agentbricks/templates/agent-openai/agent/mcps.py +28 -0
  63. databricks_agentbricks/templates/agent-openai/agent/tools/__init__.py +24 -0
  64. databricks_agentbricks/templates/agent-openai/agent/tools/sample_tool.py +15 -0
  65. databricks_agentbricks/templates/agent-openai/agent/tools/send_message.py +18 -0
  66. databricks_agentbricks/templates/agent-openai/app.yaml +15 -0
  67. databricks_agentbricks/templates/agent-openai/pyproject.toml +42 -0
  68. databricks_agentbricks/templates/agent-openai/runtime/__init__.py +0 -0
  69. databricks_agentbricks/templates/agent-openai/runtime/adapter.py +188 -0
  70. databricks_agentbricks/templates/agent-openai/runtime/main.py +25 -0
  71. databricks_agentbricks/templates/agent-openai/tests/__init__.py +0 -0
  72. databricks_agentbricks/templates/agent-openai/tests/test_agent.py +326 -0
  73. databricks_agentbricks/templates/agent-openai/tests/test_runtime.py +19 -0
  74. databricks_agentbricks/templates/agent-openai/tests/test_workspace.py +28 -0
  75. databricks_agentbricks/templates/custom-agent-langgraph/.env.example +1 -0
  76. databricks_agentbricks/templates/custom-agent-langgraph/.gitignore +11 -0
  77. databricks_agentbricks/templates/custom-agent-langgraph/README.md +22 -0
  78. databricks_agentbricks/templates/custom-agent-langgraph/agent/__init__.py +0 -0
  79. databricks_agentbricks/templates/custom-agent-langgraph/agent/agent.py +27 -0
  80. databricks_agentbricks/templates/custom-agent-langgraph/app.yaml +1 -0
  81. databricks_agentbricks/templates/custom-agent-langgraph/pyproject.toml +38 -0
  82. databricks_agentbricks/templates/custom-agent-langgraph/runtime/__init__.py +0 -0
  83. databricks_agentbricks/templates/custom-agent-langgraph/runtime/main.py +31 -0
  84. databricks_agentbricks/templates/custom-agent-langgraph/tests/test_runtime.py +29 -0
  85. databricks_agentbricks/templates/custom-agent-openai/.env.example +1 -0
  86. databricks_agentbricks/templates/custom-agent-openai/.gitignore +11 -0
  87. databricks_agentbricks/templates/custom-agent-openai/README.md +22 -0
  88. databricks_agentbricks/templates/custom-agent-openai/agent/__init__.py +0 -0
  89. databricks_agentbricks/templates/custom-agent-openai/agent/agent.py +29 -0
  90. databricks_agentbricks/templates/custom-agent-openai/app.yaml +1 -0
  91. databricks_agentbricks/templates/custom-agent-openai/pyproject.toml +38 -0
  92. databricks_agentbricks/templates/custom-agent-openai/runtime/__init__.py +0 -0
  93. databricks_agentbricks/templates/custom-agent-openai/runtime/main.py +31 -0
  94. databricks_agentbricks/templates/custom-agent-openai/tests/test_runtime.py +29 -0
  95. databricks_agentbricks/templates/sandbox_mcp.py +39 -0
  96. databricks_agentbricks/templates/sandbox_mcp_langgraph.py +39 -0
  97. databricks_agentbricks/templates/ui/agent-langgraph/CHAT_APP.md +49 -0
  98. databricks_agentbricks/templates/ui/agent-langgraph/runtime/main.py +26 -0
  99. databricks_agentbricks/templates/ui/agent-langgraph/runtime/ui.py +528 -0
  100. databricks_agentbricks/templates/ui/agent-langgraph/tests/test_demo_ui.py +542 -0
  101. databricks_agentbricks/templates/ui/agent-langgraph/ui/agentbricks-mark.png +0 -0
  102. databricks_agentbricks/templates/ui/agent-langgraph/ui/app.js +1464 -0
  103. databricks_agentbricks/templates/ui/agent-langgraph/ui/index.html +281 -0
  104. databricks_agentbricks/templates/ui/agent-langgraph/ui/styles.css +1913 -0
  105. databricks_agentbricks/templates/ui/agent-openai/CHAT_APP.md +54 -0
  106. databricks_agentbricks/templates/ui/agent-openai/runtime/main.py +26 -0
  107. databricks_agentbricks/templates/ui/agent-openai/runtime/ui.py +521 -0
  108. databricks_agentbricks/templates/ui/agent-openai/tests/test_demo_ui.py +512 -0
  109. databricks_agentbricks/templates/ui/agent-openai/ui/agentbricks-mark.png +0 -0
  110. databricks_agentbricks/templates/ui/agent-openai/ui/app.js +1428 -0
  111. databricks_agentbricks/templates/ui/agent-openai/ui/index.html +281 -0
  112. databricks_agentbricks/templates/ui/agent-openai/ui/styles.css +1913 -0
  113. databricks_agentbricks/theme.py +72 -0
  114. databricks_agentbricks-0.2.0.dist-info/METADATA +897 -0
  115. databricks_agentbricks-0.2.0.dist-info/RECORD +159 -0
  116. databricks_agentbricks-0.2.0.dist-info/WHEEL +4 -0
  117. databricks_agentbricks-0.2.0.dist-info/entry_points.txt +2 -0
  118. databricks_agentbricks-0.2.0.dist-info/licenses/NOTICE +39 -0
  119. databricks_agentkit/__init__.py +67 -0
  120. databricks_agentkit/_api_client.py +673 -0
  121. databricks_agentkit/_pagination.py +25 -0
  122. databricks_agentkit/client.py +31 -0
  123. databricks_agentkit/langgraph/__init__.py +98 -0
  124. databricks_agentkit/langgraph/genie.py +50 -0
  125. databricks_agentkit/langgraph/mcp.py +216 -0
  126. databricks_agentkit/langgraph/memory.py +67 -0
  127. databricks_agentkit/langgraph/session_store.py +485 -0
  128. databricks_agentkit/memory_store.py +371 -0
  129. databricks_agentkit/models.py +330 -0
  130. databricks_agentkit/openai/__init__.py +93 -0
  131. databricks_agentkit/openai/genie.py +50 -0
  132. databricks_agentkit/openai/mcp.py +189 -0
  133. databricks_agentkit/openai/memory.py +67 -0
  134. databricks_agentkit/openai/sessions.py +157 -0
  135. databricks_agentkit/py.typed +0 -0
  136. databricks_agentkit/runtime/ARCHITECTURE.md +64 -0
  137. databricks_agentkit/runtime/README.md +184 -0
  138. databricks_agentkit/runtime/__init__.py +69 -0
  139. databricks_agentkit/runtime/app.py +316 -0
  140. databricks_agentkit/runtime/auth.py +149 -0
  141. databricks_agentkit/runtime/durability/__init__.py +1 -0
  142. databricks_agentkit/runtime/durability/execution.py +133 -0
  143. databricks_agentkit/runtime/durability/heartbeat.py +64 -0
  144. databricks_agentkit/runtime/durability/lakebase_runtime_store.py +648 -0
  145. databricks_agentkit/runtime/durability/recovery.py +70 -0
  146. databricks_agentkit/runtime/durability/store.py +33 -0
  147. databricks_agentkit/runtime/execution.py +160 -0
  148. databricks_agentkit/runtime/genie.py +224 -0
  149. databricks_agentkit/runtime/mcp_auth.py +114 -0
  150. databricks_agentkit/runtime/model_services.py +90 -0
  151. databricks_agentkit/runtime/runtime.py +273 -0
  152. databricks_agentkit/runtime/session_store_client.py +120 -0
  153. databricks_agentkit/runtime/store.py +287 -0
  154. databricks_agentkit/runtime/tool_manifest.py +223 -0
  155. databricks_agentkit/runtime/tracing.py +71 -0
  156. databricks_agentkit/runtime/types.py +118 -0
  157. databricks_agentkit/runtime/workspace.py +27 -0
  158. databricks_agentkit/session_store.py +434 -0
  159. databricks_agentkit/timefmt.py +85 -0
@@ -0,0 +1,31 @@
1
+ """Ergonomic resource wrapper over the generated service API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Optional
6
+
7
+ from databricks_agentkit._api_client import _AgentBricksApiClient
8
+ from databricks_agentkit.memory_store import MemoryStores
9
+ from databricks_agentkit.session_store import SessionStores
10
+
11
+ if TYPE_CHECKING:
12
+ # Imported lazily elsewhere: pulling databricks.sdk costs ~0.7s, so keeping it out of the
13
+ # import path lets local CLI commands (help, init, tools, bind, completion) start fast.
14
+ from databricks.sdk import WorkspaceClient
15
+
16
+
17
+ class AgentKitClient:
18
+ """High-level client for AgentKit memory and session APIs.
19
+
20
+ ``WorkspaceClient.mason`` will replace the private transport once the generated SDK is
21
+ released; the resource-oriented public surface remains unchanged.
22
+
23
+ Args:
24
+ workspace_client: An authenticated Databricks workspace client. When omitted,
25
+ the Databricks SDK's default authentication resolution is used.
26
+ """
27
+
28
+ def __init__(self, workspace_client: Optional[WorkspaceClient] = None) -> None:
29
+ api = _AgentBricksApiClient(workspace_client=workspace_client)
30
+ self.memory_stores = MemoryStores(api)
31
+ self.session_stores = SessionStores(api)
@@ -0,0 +1,98 @@
1
+ """LangGraph adapter for running an agent on Databricks (installed via ``databricks-agentbricks[langgraph]``).
2
+
3
+ Composable pieces you drop into an existing LangGraph agent — a session-store checkpointer, MCP
4
+ tools declared in ``agent.toml``, long-term memory tools, and MLflow tracing. Each maps onto a slot
5
+ LangGraph already has, so migrating an existing agent is a graft, not a rewrite::
6
+
7
+ from databricks_agentkit.langgraph import (
8
+ checkpointer,
9
+ thread_config,
10
+ mcp_tools,
11
+ configure_tracing,
12
+ )
13
+
14
+ configure_tracing()
15
+ agent = create_agent(
16
+ model=...,
17
+ tools=[
18
+ *your_tools,
19
+ *await mcp_tools(),
20
+ ], # mcp_tools() includes your agent.toml servers
21
+ checkpointer=checkpointer(), # durable when AGENT_SESSION_STORE is set
22
+ )
23
+ result = await agent.ainvoke(inputs, config=thread_config(session_id))
24
+
25
+ These need the agent stack (databricks-langchain, langgraph, langchain, fastapi, mlflow), so they sit
26
+ behind the ``[langgraph]`` extra to keep a plain ``databricks-agentbricks`` installation independent of agent frameworks.
27
+
28
+ ``__all__`` is the curated surface. Other entry points (``mcp_client``, ``DatabricksSessionStoreSaver``)
29
+ are reachable by their submodule paths but not re-exported here.
30
+ """
31
+
32
+ from typing import TYPE_CHECKING
33
+
34
+ if TYPE_CHECKING:
35
+ from databricks_agentkit.langgraph.genie import genie_tools
36
+ from databricks_agentkit.langgraph.mcp import mcp_tools
37
+ from databricks_agentkit.langgraph.memory import memory_tools
38
+ from databricks_agentkit.langgraph.session_store import checkpointer, thread_config
39
+ from databricks_agentkit.runtime import (
40
+ start_trace,
41
+ workspace_client,
42
+ workspace_headers,
43
+ )
44
+
45
+
46
+ def configure_tracing() -> None:
47
+ """Enable MLflow tracing with LangChain autologging. Call once at startup.
48
+
49
+ Safe to call unconditionally — tracing turns on only when the MLflow destination and experiment
50
+ are configured in the environment (see :func:`databricks_agentkit.runtime.configure_tracing`).
51
+ """
52
+ import mlflow
53
+
54
+ from databricks_agentkit.runtime import configure_tracing as _configure_tracing
55
+
56
+ _configure_tracing(autolog=mlflow.langchain.autolog)
57
+
58
+
59
+ __all__ = [
60
+ "genie_tools",
61
+ # MCP tools from agent.toml (plus any servers you pass) — add them to your agent's tool list.
62
+ "mcp_tools",
63
+ # Long-term memory tools (opt-in via AGENT_MEMORY_STORE) — add to your tool list.
64
+ "memory_tools",
65
+ # Session persistence — pass checkpointer() to create_agent(checkpointer=...) and
66
+ # thread_config(session_id) as the per-request run config.
67
+ "checkpointer",
68
+ "thread_config",
69
+ # MLflow tracing (LangChain autolog bound in) — call configure_tracing() once at startup.
70
+ "configure_tracing",
71
+ # Wrap each invocation in start_trace() so a trace is recorded (pass session_id= to tag it).
72
+ "start_trace",
73
+ # Workspace SDK client construction.
74
+ "workspace_client",
75
+ "workspace_headers",
76
+ ]
77
+
78
+ # Re-exports resolved lazily (PEP 562) so importing one submodule (e.g. ``.mcp``) does not eagerly
79
+ # pull in the others' dependencies. ``configure_tracing`` is defined above (binds LangChain autolog).
80
+ _MODULE_BY_NAME = {
81
+ "genie_tools": "databricks_agentkit.langgraph.genie",
82
+ "mcp_tools": "databricks_agentkit.langgraph.mcp",
83
+ "memory_tools": "databricks_agentkit.langgraph.memory",
84
+ "checkpointer": "databricks_agentkit.langgraph.session_store",
85
+ "thread_config": "databricks_agentkit.langgraph.session_store",
86
+ "start_trace": "databricks_agentkit.runtime",
87
+ "workspace_client": "databricks_agentkit.runtime",
88
+ "workspace_headers": "databricks_agentkit.runtime",
89
+ }
90
+
91
+
92
+ def __getattr__(name: str) -> object:
93
+ module = _MODULE_BY_NAME.get(name)
94
+ if module is None:
95
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
96
+ import importlib
97
+
98
+ return getattr(importlib.import_module(module), name)
@@ -0,0 +1,50 @@
1
+ """Native Genie Agent tools for LangGraph, configured through ``agent.toml``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from collections.abc import Callable
7
+ from typing import TYPE_CHECKING
8
+
9
+ from langchain_core.tools import BaseTool, tool
10
+
11
+ from databricks_agentkit.runtime.auth import AuthError
12
+ from databricks_agentkit.runtime.genie import GenieAgent
13
+ from databricks_agentkit.runtime.tool_manifest import load_tools
14
+
15
+ if TYPE_CHECKING:
16
+ from databricks.sdk import WorkspaceClient
17
+
18
+
19
+ def genie_tools(
20
+ *, workspace_client_for: Callable[[str], WorkspaceClient] | None = None
21
+ ) -> list[BaseTool]:
22
+ """Build ask, poll and query-result tools for each configured Genie Agent.
23
+
24
+ Space IDs are bound from the manifest, not exposed as model arguments. Construction performs
25
+ no authentication or network requests; an agent with no Genie Agent bindings gets no tools.
26
+ """
27
+ tools = []
28
+ for record in load_tools(expected_framework="langgraph"):
29
+ if record.kind != "genie_agent":
30
+ continue
31
+ if (
32
+ record.auth == "user"
33
+ and workspace_client_for is None
34
+ and os.getenv("DATABRICKS_APP_NAME")
35
+ ):
36
+ raise AuthError(
37
+ "MCP_USER_AUTHORIZATION_MISSING",
38
+ "This deployed Genie integration requires request-user authorization.",
39
+ integration_id=record.id,
40
+ )
41
+ agent = GenieAgent(
42
+ record.space_id or "",
43
+ auth=record.auth or "app",
44
+ workspace_client_for=workspace_client_for,
45
+ )
46
+ tools.extend(
47
+ tool(f"{record.id}_{method.__name__}")(method)
48
+ for method in (agent.ask, agent.poll, agent.query_result)
49
+ )
50
+ return tools
@@ -0,0 +1,216 @@
1
+ """Build MCP tools for the agent from the servers declared in ``agent.toml`` (plus any the agent adds).
2
+
3
+ ``mcp_tools()`` is the entry point: it reads the MCP servers declared in ``agent.toml``
4
+ (sandbox/mcp + uc_function), fetches their LangChain tools with sandbox downscoping applied, and
5
+ returns them. An agent with its own hand-built servers passes them as ``extra_servers``; leaving
6
+ ``agent.toml`` empty simply yields no declared servers. Typical agent use::
7
+
8
+ tools = await mcp_tools() # just the agent.toml servers
9
+ tools = await mcp_tools(build_mcp_servers()) # agent.toml servers + the agent's own
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import asyncio
15
+ import logging
16
+ from typing import TYPE_CHECKING, Any
17
+
18
+ from databricks_langchain import DatabricksMCPServer, DatabricksMultiServerMCPClient
19
+ from langchain_mcp_adapters.sessions import create_session
20
+
21
+ if TYPE_CHECKING:
22
+ from databricks_langchain import MCPServer
23
+
24
+ from databricks_agentkit.runtime import mcp_auth
25
+ from databricks_agentkit.runtime.auth import AuthError
26
+ from databricks_agentkit.runtime.tool_manifest import ToolRecord, downscope_wire, load_tools
27
+ from databricks_agentkit.runtime.workspace import workspace_client, workspace_headers
28
+
29
+ logger = logging.getLogger(__name__)
30
+ _auth_error = mcp_auth.mcp_auth_error
31
+ _tool_error = mcp_auth.mcp_tool_error
32
+
33
+
34
+ def _server_from_tool(
35
+ tool: ToolRecord,
36
+ *,
37
+ workspace_client_for: mcp_auth.WorkspaceClientResolver | None = None,
38
+ ) -> DatabricksMCPServer | None:
39
+ if tool.kind not in {"sandbox", "mcp", "uc_function", "genie_one"}:
40
+ return None
41
+ mode = tool.auth or "app"
42
+ client = mcp_auth.resolve_mcp_workspace_client(
43
+ mode, tool.id, workspace_client_for, workspace_client
44
+ )
45
+ host = client.config.host.rstrip("/")
46
+ if tool.kind in {"sandbox", "mcp", "genie_one"}:
47
+ url = (
48
+ f"{host}/api/2.0/mcp/genie"
49
+ if tool.kind == "genie_one"
50
+ else f"{host}/ai-gateway/mcp-services/{tool.service}"
51
+ )
52
+ return DatabricksMCPServer(
53
+ name=tool.id,
54
+ url=url,
55
+ headers=workspace_headers() or None,
56
+ workspace_client=client,
57
+ timeout=120.0,
58
+ )
59
+ if tool.kind == "uc_function":
60
+ catalog, schema, function_name = (tool.function or "").split(".")
61
+ return DatabricksMCPServer.from_uc_function(
62
+ catalog=catalog,
63
+ schema=schema,
64
+ function_name=function_name,
65
+ name=tool.id,
66
+ headers=workspace_headers() or None,
67
+ workspace_client=client,
68
+ timeout=120.0,
69
+ )
70
+ return None
71
+
72
+
73
+ def _declared_servers(
74
+ *,
75
+ workspace_client_for: mcp_auth.WorkspaceClientResolver | None = None,
76
+ ) -> list[DatabricksMCPServer]:
77
+ """The MCP servers declared in the agent's ``agent.toml`` (may be empty)."""
78
+ tools = load_tools(expected_framework="langgraph")
79
+ return [
80
+ server
81
+ for tool in tools
82
+ if (server := _server_from_tool(tool, workspace_client_for=workspace_client_for))
83
+ is not None
84
+ ]
85
+
86
+
87
+ def _sandbox_interceptor(
88
+ tools: tuple[ToolRecord, ...],
89
+ *,
90
+ workspace_client_for: mcp_auth.WorkspaceClientResolver | None = None,
91
+ ):
92
+ declared = {tool.id: tool for tool in tools}
93
+
94
+ async def interceptor(request: Any, handler: Any) -> Any:
95
+ tool = declared.get(request.server_name)
96
+ if tool is None:
97
+ return await handler(request)
98
+ request_user = tool.auth == "user"
99
+ try:
100
+ if tool.kind == "sandbox":
101
+ server = _server_from_tool(tool, workspace_client_for=workspace_client_for)
102
+ if server is None:
103
+ raise RuntimeError(f"Could not build sandbox MCP server {tool.id!r}.")
104
+ async with create_session(server.to_connection_dict()) as session:
105
+ await session.initialize()
106
+ result = await session.call_tool(
107
+ request.name,
108
+ request.args,
109
+ meta={"downscope": downscope_wire(tool)},
110
+ )
111
+ else:
112
+ result = await handler(request)
113
+ except Exception as error:
114
+ if request_user:
115
+ raise _auth_error(error, tool.id) or AuthError(
116
+ "MCP_TOOL_FAILED", "The configured MCP tool failed.", 502, tool.id
117
+ ) from None
118
+ raise
119
+ if (
120
+ request_user
121
+ and getattr(result, "isError", False)
122
+ and (error := _tool_error(result, tool.id))
123
+ ):
124
+ raise error
125
+ return result
126
+
127
+ return interceptor
128
+
129
+
130
+ def mcp_client(
131
+ servers: list[DatabricksMCPServer],
132
+ *,
133
+ workspace_client_for: mcp_auth.WorkspaceClientResolver | None = None,
134
+ tools: tuple[ToolRecord, ...] | None = None,
135
+ ) -> DatabricksMultiServerMCPClient:
136
+ """A multi-server MCP client over ``servers`` with the sandbox downscoping interceptor attached.
137
+
138
+ The interceptor is derived from the ``agent.toml`` manifest, so sandbox tools run downscoped
139
+ regardless of how the caller drives the returned client (``get_tools`` or otherwise). Callers who
140
+ build their own client instead take on applying downscoping themselves.
141
+ """
142
+ snapshot = tuple(load_tools(expected_framework="langgraph")) if tools is None else tools
143
+ interceptors = (
144
+ [_sandbox_interceptor(snapshot, workspace_client_for=workspace_client_for)]
145
+ if snapshot
146
+ else []
147
+ )
148
+ # DatabricksMCPServer is a subclass of MCPServer, so coerce the type for the API
149
+ servers_as_mcp: list[MCPServer] = servers # type: ignore[name-defined,assignment]
150
+ return DatabricksMultiServerMCPClient(
151
+ servers_as_mcp, tool_interceptors=interceptors, handle_tool_errors=True
152
+ )
153
+
154
+
155
+ async def mcp_tools(
156
+ extra_servers: list[DatabricksMCPServer] | None = None,
157
+ *,
158
+ workspace_client_for: mcp_auth.WorkspaceClientResolver | None = None,
159
+ ) -> list:
160
+ """Fetch declared tools with request identity and protected sandbox downscoping.
161
+
162
+ Includes the MCP servers declared in ``agent.toml``; pass ``extra_servers`` to add servers the
163
+ agent builds itself. Request-user failures propagate; App, legacy, and optional customer
164
+ servers retain their existing identity and per-server best-effort discovery behavior.
165
+ """
166
+ snapshot = tuple(load_tools(expected_framework="langgraph"))
167
+ servers = [
168
+ server
169
+ for tool in snapshot
170
+ if (server := _server_from_tool(tool, workspace_client_for=workspace_client_for))
171
+ is not None
172
+ ]
173
+ result = []
174
+ if servers:
175
+ client = mcp_client(servers, workspace_client_for=workspace_client_for, tools=snapshot)
176
+ request_user = {tool.id for tool in snapshot if tool.auth == "user"}
177
+
178
+ async def fetch_declared(server: DatabricksMCPServer) -> list:
179
+ try:
180
+ return await client.get_tools(server_name=server.name)
181
+ except Exception as error:
182
+ if server.name in request_user:
183
+ raise _auth_error(error, server.name) or AuthError(
184
+ "MCP_TOOL_FAILED",
185
+ "Could not discover configured MCP tools.",
186
+ 502,
187
+ server.name,
188
+ ) from None
189
+ logger.warning(
190
+ "Failed to fetch MCP tools from server %r; continuing without it.", server.name
191
+ )
192
+ return []
193
+
194
+ groups = await asyncio.gather(
195
+ *(fetch_declared(server) for server in servers), return_exceptions=True
196
+ )
197
+ for group in groups:
198
+ if isinstance(group, BaseException):
199
+ raise group
200
+ result.extend(group)
201
+ if extra_servers:
202
+ optional_client = mcp_client(extra_servers, tools=())
203
+
204
+ async def fetch_optional(server: DatabricksMCPServer) -> list:
205
+ try:
206
+ return await optional_client.get_tools(server_name=server.name)
207
+ except Exception:
208
+ logger.warning(
209
+ "Failed to fetch optional MCP tools from server %r; continuing without it.",
210
+ server.name,
211
+ )
212
+ return []
213
+
214
+ for group in await asyncio.gather(*(fetch_optional(server) for server in extra_servers)):
215
+ result.extend(group)
216
+ return result
@@ -0,0 +1,67 @@
1
+ """Long-term memory tools — opt-in, gated on ``AGENT_MEMORY_STORE``.
2
+
3
+ Unlike the session store (short-term transcript for one conversation), long-term memory is exposed
4
+ to the model as two tools — ``remember`` and ``recall`` — over the Databricks managed memory store's
5
+ ``2.0/agents`` entries API. Facts persist across conversations.
6
+
7
+ ``memory_tools(actor)`` returns the tools when ``AGENT_MEMORY_STORE`` is set, else an empty list, so
8
+ the model never sees them when memory is unconfigured. ``create_agent_graph`` composes them into its
9
+ tool list. This is a stand-in for a future ``databricks-langchain`` memory helper — when the SDK
10
+ provides one, swap the import in ``agent.py`` and delete this file.
11
+
12
+ Memory entries are per-actor. ``actor`` is the identity whose memory these tools read and write — the
13
+ caller (the agent server) supplies it, typically the signed-in user, so each user gets their own
14
+ long-term memory. It is **closed over**, not a tool argument: the model only ever sees ``remember``'s
15
+ and ``recall``'s own parameters and cannot set or spoof the actor. Pass a fixed value for one shared
16
+ memory (e.g. a team knowledge base).
17
+ """
18
+
19
+ from langchain_core.tools import BaseTool, tool
20
+
21
+ from databricks_agentkit.runtime.workspace import workspace_client
22
+
23
+ _AGENTS_API = "/api/2.0/agents"
24
+
25
+
26
+ def _api():
27
+ # Build the client lazily (needs workspace auth) so importing this module stays cheap.
28
+ return workspace_client().api_client
29
+
30
+
31
+ def memory_tools(actor: str, store: str | None = None) -> list[BaseTool]:
32
+ """The long-term-memory tools for ``actor`` when a memory store is configured, else none.
33
+
34
+ The store resolves ``store`` arg → ``AGENT_MEMORY_STORE`` env → the ``[memory_store]`` binding in
35
+ agent.toml (`ab memory bind`) → none (no tools). ``actor`` partitions the store; it is captured
36
+ in the tools' closures (not exposed to the model). Call this per request with the identity whose
37
+ memory to use (e.g. the signed-in user).
38
+ """
39
+ from databricks_agentkit.runtime.tool_manifest import resolve_memory_store
40
+
41
+ store = resolve_memory_store(store)
42
+ if not store:
43
+ return []
44
+ store_path = f"{_AGENTS_API}/memory-stores/{store}"
45
+
46
+ @tool
47
+ def remember(fact: str, topic: str) -> str:
48
+ """Persist a durable fact about the user in long-term memory."""
49
+ _api().do(
50
+ "POST",
51
+ f"{store_path}/entries",
52
+ body={"actor_id": actor, "path": f"/{topic}/{fact[:8]}.md", "content": fact},
53
+ )
54
+ return "stored"
55
+
56
+ @tool
57
+ def recall(query: str) -> str:
58
+ """Search the user's long-term memory for facts relevant to the query."""
59
+ data = _api().do(
60
+ "POST",
61
+ f"{store_path}/entries:search",
62
+ body={"actor_id": actor, "query": query, "limit": 5},
63
+ )
64
+ entries = data.get("managed_memory_entries") or []
65
+ return "\n".join(f"- {e.get('content')}" for e in entries) or "No relevant memories."
66
+
67
+ return [remember, recall]