主题
管理连接
存储 API 密钥和 OAuth 授权,以便 Managed Deep Agents 在运行时对外部服务身份验证。
一个连接把一个受管深 agent 关联到一个外部服务,例如 GitHub、Notion 或 Tavily。凭据存在你的 LangSmith 工作区里。在一个自写工具或 MCP 服务器定义里用 connections.get(...) 在运行时解析它。
连接让你能:
- 把凭据挡在你项目之外:
mda deploy在每次部署时把.env值收集为部署机密。而一个连接的值留在工作区里。 - 给每个调用者他们自己的身份:一个用户所有的连接解析提出请求那人的凭据,所以 agent 读那个人的文档并以他们的名义行动。一个环境变量对所有人只有一个值。
- 跳过 OAuth 管线:Managed Deep Agents 运行授权往返,所以项目不需要回调路由、令牌存储或同意屏。这些流程由你的 Managed Deep Agent 自动处理。
- 跨 agent 复用一个 slug:连接属于工作区,所以多个部署能解析同一个 slug。每个部署在该 slug 下持有它自己的凭据:从每个项目根运行
mda connections create <slug>一次。轮换一个部署的值在其下次运行时生效,无需重新部署。
把一个凭据存在 LangSmith 中、跨你的 Managed Deep Agents 使用,只需一条命令:
bash
uv run mda connections create organization-tavily --secret-from-env TAVILY_API_KEY而读取它只需一行:
python
api_key = await connections.get("organization-tavily", {"type": "agent"})本页其余部分讲述围绕这些步骤的两个选择:谁拥有凭据,以及服务如何身份验证。
NOTE
Managed Deep Agents 处于公开 beta 阶段,仅在 US 区域的 LangSmith Cloud 上可用。
选择一个凭据所有者
每个 connections.get(...) 调用都点名它所解析凭据的拥有者:
| 所有者 | 解析为 | 何时使用 |
|---|---|---|
agent | 一个属于该部署的凭据。每个调用者都用它。 | 每个调用者都需要同一种能力:用 Tavily 做网页搜索、一个共享知识库,或向一个团队频道发帖。 |
user | 调用者本人的凭据。每人授权他们自己的账户。 | agent 以提问者的身份行动:搜索只有他们能看见的 Notion 页面、以他们的名义提一个 issue,或以他们的身头发邮件。 |
python
tavily_key = await connections.get("organization-tavily", {"type": "agent"})
notion_token = await connections.get("engineering-notion", {"type": "user"})connections.get(...) 把一个凭据值解析为字符串。
一个连接是一个容器,且它属于工作区而非某个所有者。它内部的凭据有所有者。没有 --owner 标志:你用的创建模式设定所有者。
connections.get(...) 选择一个所有者。它不创建一个。为一个没有自己凭据的所有者请求,对 agent 会使该运行失败,对 user 抛出一个 credential_authorization_required 中断。在同一个 slug 下为另一个所有者存储的凭据不满足这次查找。
agent 所有的凭据属于项目的部署,所以至少一次成功的 mda deploy 之后再创建它们。一个部署只读它自己拥有的凭据。要从第二个部署解析同一个 slug,从那个项目根再运行一次 mda connections create <slug>:在一个已存在的 slug 上,该命令附加一个新的 agent 所有凭据而非失败。
识别调用者
一个用户所有的连接针对 Managed Deep Agents 附加到运行的调用者身份来解析。那个身份来自哪里,取决于 agent 如何被调用:
| 界面 | 调用者身份 |
|---|---|
| Slack 频道 | 发消息的 Slack 用户。 |
| LangSmith Studio | 已登录的 LangSmith 用户。 |
| SDK 客户端或自定义前端 | 项目的身份声明所身份验证的那个身份。 |
默认的身份声明验证一个 LangSmith API 密钥。那个密钥身份验证的是调用客户端,而非某个个人,所以每个出示它的调用者都解析到同一个身份。要给每个登录的人他们自己的凭据,声明 Supabase 身份。
代码从不向 connections.get(...) 传一个用户 ID。运行时解析调用者并返回那人的凭据。
NOTE
Slack 和 Studio 会为调用者完成授权往返。对自定义频道的一流支持尚在开发中。今天要以调用者身份构建一个自定义前端,见处理授权中断并联系 LangChain 团队。
选择一个创建模式
所有权决定 agent 如何处置一个凭据。创建模式决定那个凭据如何到达工作区,并取决于外部服务如何身份验证:
| 模式 | 何时使用 | 用什么创建 | 凭据所有者 |
|---|---|---|---|
| 不透明机密 | 服务用一个固定 API 密钥或其他静态机密。 | --secret-from-env、--secret-from-file、stdin,或一个交互式提示 | agent |
| 通用 OAuth | 你注册你自己的 OAuth 应用(BYOT),例如 GitHub 或 Google。 | 从目录用 --oauth <service>,或对自定义提供商用 --authorize-url 和 --token-url | 每个调用者,或 agent 配 --authorize 或客户端凭据 |
| MCP OAuth | 一个 MCP 服务器公布 OAuth 并自动注册一个客户端。 | --mcp <url>,或当项目已声明那个用户所有的 MCP 服务器时只用 slug | 每个调用者,或 agent 配 --authorize |
例如:
bash
# Opaque secret: store a fixed API key for the agent
uv run mda connections create organization-tavily --secret-from-env TAVILY_API_KEY
# General OAuth: register your own app, and let each caller authorize
uv run mda connections create frontend-github --oauth github \
--client-id "********" --secret-from-env GITHUB_CLIENT_SECRET
# MCP OAuth: let the MCP server register a client for you
uv run mda connections create engineering-notion --mcp https://mcp.notion.com/mcp不要在一个命令里混用模式。例如,--mcp 不能与 --oauth、自定义端点、--client-id 或一个机密值组合。
为连接命名
mda connections create 的第一个参数是一个 slug:你对这个连接起的名字,也是你的代码传给 connections.get(...) 的名字。它不是提供商名,提供商名归 --oauth:
bash
mda connections create frontend-github --oauth github
# ^ your slug ^ catalog providerslug 在一个工作区内唯一。用小写字母、数字和单个连字符,并选一个能识别连接指向哪个账户的名字,例如 organization-tavily 或 engineering-notion。
创建一个不透明机密
为 agent 存储一个固定机密。从环境变量、文件、stdin 或提示读取值。这个模式适合自定义工具的 API 密钥,例如一个 Tavily 搜索密钥。用 CLI 创建的不透明机密是 agent 所有的。
把源值放进 shell 环境或项目 .env 文件,然后从那个变量创建连接。从项目根运行该命令。CLI 需要一个 LangSmith API 密钥和工作区 ID。
bash
uv run mda connections create organization-tavily --secret-from-env TAVILY_API_KEY这会把 TAVILY_API_KEY 的值存为一个 agent 所有的机密。运行时代码用 connections.get(...) 解析它。
提供该值的其他方式:
--secret-from-file PATH:从一个只含该机密的文件读一个值。- stdin:用管道或重定向传入值,例如
printf '%s' "$ACME_API_KEY" | mda connections create acme-api。 - 交互式提示:在 TTY 上省略值标志。CLI 会隐藏输入,所以机密不出现在屏幕或 shell 历史里。
用 CLI 创建的不透明机密总是 agent 所有的。要按调用者的凭据,用一个 OAuth 连接。
签发一个属于 agent 而非复用个人密钥的密钥。一个专属密钥能被限定作用域、轮换和撤销,而不影响共享它的其他任何东西。
在自定义工具中用一个不透明机密
下面的工具为 agent 解析 organization-tavily 连接,然后把它发送给 Tavily API:
python
import httpx
from langchain.tools import tool
from managed_deepagents import connections
@tool(parse_docstring=True)
async def search_web(query: str) -> str:
"""
Search the web.
Args:
query: Search query.
"""
api_key = await connections.get("organization-tavily", {"type": "agent"})
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
"https://api.tavily.com/search",
json={"api_key": api_key, "query": query, "max_results": 5},
)
response.raise_for_status()
return response.text把该工具加到 agent,见自定义工具。
创建一个通用 OAuth 连接
注册一个自带应用(BYOT)的 OAuth 客户端,以便调用者能授予 agent 访问一个提供商 API 的权限。在你拥有 OAuth 应用注册的 REST 或 GraphQL API 上用这个模式。对于自动发现和注册客户端的 MCP 服务器,改用 MCP OAuth。
使用 OAuth 目录
每个 OAuth 提供商都需要同样的五项设置:一个授权 URL、一个令牌 URL、一个令牌端点身份验证方法、授权参数和默认作用域。一个目录条目提供全部五项,所以 --oauth <service> 加你自己的客户端 ID 和机密就是整个配置。
这个目录不是对你能用哪些提供商的一道关卡。对它未覆盖的服务,自己传端点。
用以下命令列出目录:
bash
uv run mda connections catalog该命令打印每个服务的默认作用域和你在其中注册一个应用的页面。它不调用 LangSmith,所以不需要工作区 ID 或 API 密钥。把第一列中的一个值传给 --oauth。
--oauth 值 | 注册一个应用 | 默认作用域 |
|---|---|---|
atlassian | Atlassian | read:me offline_access |
bitbucket | Bitbucket | account |
box | Box | 无 |
click-up | ClickUp | 无 |
discord | Discord | identify email |
dropbox | Dropbox | account_info.read |
facebook | email public_profile | |
figma | Figma | current_user:read |
github | GitHub | read:user |
gitlab | GitLab | read_user |
google | openid https://www.googleapis.com/auth/userinfo.email | |
hubspot | HubSpot | oauth |
huggingface | Hugging Face | openid profile email |
linear | Linear | read |
linkedin | openid profile email | |
notion-api | Notion API | 无 |
patreon | Patreon | identity identity[email] |
reddit | identity | |
salesforce | Salesforce | api refresh_token |
slack | Slack | chat:write |
spotify | Spotify | user-read-email |
stripe-link | Stripe Link | payment_methods.agentic userinfo:read |
twitch | Twitch | user:read:email |
x | X | tweet.read users.read offline.access |
一个没有默认作用域的服务需要 --scope。
某些服务为它们的 API 和它们的 MCP 服务器运行各自独立的 OAuth 应用
Notion 就是其一。--oauth notion-api 针对 Notion 的 REST API 注册一个应用,而那个应用不授权 Notion 的 MCP 服务器。要用 MCP 服务器,请改创建一个 MCP OAuth 连接。当一个服务两者都提供时查阅提供商的文档。
GitHub 需要一个 OAuth 客户端 ID 和客户端机密。设 GITHUB_CLIENT_SECRET,然后创建连接。把 ******** 替换为客户端 ID:
bash
uv run mda connections create frontend-github \
--oauth github \
--client-id "********" \
--secret-from-env GITHUB_CLIENT_SECRET创建时,CLI 打印要向提供商注册的重定向 URI。在调用者授权之前,在提供商的应用设置页注册那个 URI。该 URI 跟随你的 LangSmith 主机,例如生产上的 https://api.smith.langchain.com/v1/agent-auth/oauth/callback。
可选标志:
--scope SCOPE:替换目录默认值。对每个作用域重复。--allowed-scope SCOPE:任何后续授权可请求的上限。默认为--scope值且必须覆盖每个--scope。--authorization-param KEY=VALUE:额外的授权查询参数。对每个参数重复。--auth-method METHOD:客户端如何向令牌端点身份验证:client_secret_basic、client_secret_post,或公共客户端用none。默认为目录服务的方法。
用一个自定义端点注册一个提供商
对目录未覆盖的服务,传两个端点外加一个客户端 ID 和作用域:
bash
uv run mda connections create acme \
--authorize-url https://auth.acme.com/authorize \
--token-url https://auth.acme.com/token \
--client-id "********" \
--secret-from-env ACME_CLIENT_SECRET \
--scope read多数提供商会拒绝一个不带 scope 参数的授权请求。对手动注册传 --scope,或在目录覆盖该提供商时用 --oauth <service>。
对像 Stripe 这样在令牌端点需要自定义标头的提供商,往 mda connections create 添加 --token-request-header KEY=VALUE。对每个标头重复该标志。这个选项支持授权码和客户端凭据两种授权。标头值是只写的。
创建一个客户端凭据连接
OAuth 客户端凭据授权把 agent 作为一个应用而非一个人身份验证。传 --grant-type client_credentials,CLI 会在创建连接时请求令牌。没人通过浏览器登录,也没有调用者看到授权提示。
当提供商签发机器对机器的 OAuth 凭据时用这个模式。当提供商的 API 只以一个人身份验证时,改授权一个 agent 所有的账户。
一个客户端凭据连接需要一个令牌 URL、一个客户端 ID 和一个客户端机密。设 ACME_CLIENT_SECRET,然后创建连接。把 ******** 替换为客户端 ID:
bash
uv run mda connections create acme-api \
--grant-type client_credentials \
--token-url https://auth.acme.com/oauth/token \
--client-id "********" \
--secret-from-env ACME_CLIENT_SECRET \
--scope readCLI 打印 slug、授权类型和它所存储的作用域。从项目目录运行它,因为令牌属于那个项目的部署。当令牌请求失败时,CLI 会删除连接而非留下一个不可用的。
--oauth <service> 也有效,针对一个带令牌 URL 的目录条目。目录的默认作用域和授权参数不适用于这个授权,所以对一个需要作用域的提供商传 --scope。
可选标志:
--scope SCOPE:请求一个作用域。对每个作用域重复。--auth-method METHOD:把客户端机密作为client_secret_basic或client_secret_post发送。默认为client_secret_basic。公共客户端(none)不能用这个授权。--token-param KEY=VALUE:向令牌请求添加一个参数,例如--token-param audience=https://api.acme.com。对每个参数重复。这个授权自己设grant_type、client_id、client_secret和scope,所以那四个键会被拒绝。
这个授权拒绝授权码标志 --authorize-url、--authorization-param 和 --allowed-scope。
一个客户端凭据连接总是 agent 所有的,所以每个调用者都作为该应用行动。运行时代码像解析其他任何 agent 所有凭据那样解析它:
python
access_token = await connections.get("acme-api", {"type": "agent"})授权一个 agent 所有的 OAuth 账户
当提供商签发它自己的应用凭据(例如一个 Slack bot 令牌、一个 GitHub App 安装令牌,或一个 Notion 内部集成令牌)时,优先把它作为一个 agent 所有的机密存储。一个应用凭据被限定到应用、可单独撤销,且不依赖任何人的账户。当提供商通过 OAuth 签发那个应用身份时,创建一个客户端凭据连接。
当提供商不提供应用身份、且其 API 只以一个人身份验证时,用 --authorize。
默认情况下,一个 OAuth 连接在运行时从每个调用者收集一个授权。传 --authorize 让你自己登录一次并为该部署存储授权。此后每个调用者都作为那一个账户行动,且没有调用者看到授权提示:
bash
uv run mda connections create support-linear \
--oauth linear \
--client-id "********" \
--secret-from-env LINEAR_CLIENT_SECRET \
--scope read --scope write \
--authorizeCLI 为你登录所用的账户启动一个授权流程,并把结果授权存为该部署所有。运行时代码把它作为一个 agent 所有的凭据解析:
python
access_token = await connections.get("support-linear", {"type": "agent"})--authorize 只适用于 OAuth 连接。把它与 --oauth、自定义端点或 --mcp 组合。从项目目录运行它,因为授权属于那个项目的部署。
当每个调用者都应作为同一个共享账户而非作为他们自己行动时,用一个 agent 所有的 OAuth 账户。一个授予问答 agent 读访问的公司 Notion 账户就是一个例子。
TIP
授权一个你团队拥有的专属账户,而非你自己的。你登录所用的账户会成为 agent 为每个调用者采取的每个行动背后的身份。用一个个人账户会让你损失三样东西:
- agent 获得你在该提供商的完全访问。
- 提供商的审计日志会为 agent 所做的显示你的名字。
- 当你自己的访问改变时,agent 停止工作。
以这种方式做出的授权始终绑定到授权它的那个账户,即使是专属账户。一次密码重置、一个被撤销的会话,或一个被停用的账户都会终结该授权,所以把那个账户当作生产基础设施对待。
给那个专属账户 agent 所需的最窄 --scope 值。设 --allowed-scope 以封顶任何后续授权能请求的范围。
在自定义工具中访问一个 OAuth 令牌
这个工具解析已身份验证调用者的 GitHub 连接并调用 GitHub REST API:
python
import httpx
from langchain.tools import tool
from managed_deepagents import connections
@tool
async def get_github_user() -> str:
"""Get the authenticated caller's GitHub login."""
# Replace `frontend-github` with the name of your connection.
access_token = await connections.get("frontend-github", {"type": "user"})
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(
"https://api.github.com/user",
headers={"Authorization": f"Bearer {access_token}"},
)
response.raise_for_status()
return response.json()["login"]创建一个 MCP OAuth 连接
对于支持 OAuth 客户端注册的远程 MCP 服务器,Managed Deep Agents 会发现服务器的 OAuth 元数据并自动注册一个客户端。你不提供客户端 ID 或客户端机密。
先声明 MCP 服务器
在 tools/mcp.py 中添加该服务器并引用连接 slug。
python
from managed_deepagents import connections, define_mcp
mcp = define_mcp(
servers={
"notion": {
"transport": "http",
"url": "https://mcp.notion.com/mcp",
"connection": connections.get("engineering-notion", {"type": "user"}),
},
},
)更多信息,见连接到 MCP 服务器。
从项目声明创建
当 slug 精确匹配项目中一个用户所有的 MCP 连接时,只用 slug 创建。CLI 从 MCP 声明读服务器 URL:
bash
uv run mda connections create engineering-notion从一个显式 MCP URL 创建
当你想显式命名服务器 URL、或 slug 尚未在项目里声明时,传 --mcp:
bash
uv run mda connections create engineering-notion --mcp https://mcp.notion.com/mcp一个像 mcp.notion.com/mcp 这样无协议的值会被存为 https://mcp.notion.com/mcp。
如果服务器不能自动注册一个客户端,CLI 会如此说明并指向你改用通用 OAuth(--oauth 或手动端点)。
mda deploy 对缺失的用户所有 MCP 连接应用同样的推断。它保持现有连接不变、通过 OAuth 发现创建每个唯一缺失的 MCP 连接,并在发现或注册不可用时于部署前失败。
处理授权中断
Managed Deep Agents 为你运行 OAuth 往返。项目不需要回调路由、令牌存储、刷新逻辑或同意屏。运行暂停,调用者授予访问,运行带着那人的凭据恢复。
在第一个模型回合之前,Managed Deep Agents 检查该运行需要的每个用户所有连接。如果调用者缺一个或多个授权,凭据关卡抛出单个 LangGraph 中断。Slack 和 LangSmith Studio 为调用者处理那个中断。一个自定义前端从 LangChain 前端 SDK 读同样的载荷并在调用者连接后恢复。
用户所有的连接需要一个已身份验证的调用者。匿名或仅 agent 的运行无法完成凭据关卡。那个身份来自哪里,见识别调用者。
中断载荷
用 useStream(@langchain/react、@langchain/vue、@langchain/svelte)或 injectStream(@langchain/angular)时,待处理中断在 stream.interrupt 上。凭据关卡载荷是 stream.interrupt.value。
对一个缺失的 OAuth 授权,credentials 中每个条目都带有调用者在何处完成同意的 URL:
json
{
"type": "credential_authorization_required",
"message": "Connect the following integrations to continue.",
"credentials": [
{
"slug": "engineering-notion",
"kind": "oauth2",
"connect_url": "https://api.smith.langchain.com/v1/agent-auth/...",
"auth_id": "sess_abc123"
}
]
}一个中断列出每个缺失的授权。在恢复之前处理每个条目。一个带 "kind": "secret" 的条目表示一个用户所有的 API 密钥而非一个 OAuth 授权。Slack 和 Studio 不收集那些。更多信息,见查看当前限制。
| 字段 | 何时存在 | 含义 |
|---|---|---|
type | 总是 | 判别符。必须是 credential_authorization_required。 |
message | 总是 | 展示在待授权之上的人类可读摘要。 |
credentials | 总是 | 每个缺失授权一个条目。 |
credentials[].slug | 总是 | 要授权的连接 slug。 |
credentials[].kind | 总是 | 密码式 API 密钥为 secret,OAuth 为 oauth2。 |
credentials[].connect_url | oauth2 | 调用者完成同意的 HTTPS URL。 |
credentials[].auth_id | oauth2 | 用于轮询状态的授权会话 id。 |
在你的 UI 中读取中断
渲染这个中断的前端是一个 web 应用,所以无论 agent 用 Python 还是 TypeScript 写,这些例子都是 TypeScript。
在 stream.interrupt 上检测凭据关卡载荷,渲染一张连接卡片,然后在每个授权都被存储后用 stream.respond 恢复:
tsx
import { useStream } from "@langchain/react";
type CredentialAuthorizationRequired = {
type: "credential_authorization_required";
message?: string;
credentials: Array<{
slug: string;
kind: "oauth2" | "secret";
connect_url?: string;
auth_id?: string;
}>;
};
function isCredentialAuthorization(
value: unknown
): value is CredentialAuthorizationRequired {
return (
typeof value === "object" &&
value !== null &&
"type" in value &&
value.type === "credential_authorization_required"
);
}
export function Chat() {
const stream = useStream({
apiUrl: "https://your-deployment.example",
assistantId: "agent",
});
const payload = stream.interrupt?.value;
const credentialAuth = isCredentialAuthorization(payload) ? payload : null;
return (
<div>
{/* messages … */}
{credentialAuth && (
<ConnectCard
payload={credentialAuth}
onComplete={async (connectedSlugs) => {
await stream.respond(
{
type: "credential_authorization_completed",
connected_slugs: connectedSlugs,
},
{ interruptId: stream.interrupt?.id }
);
}}
/>
)}
</div>
);
}vue
<script setup lang="ts">
import { computed } from "vue";
import { useStream } from "@langchain/vue";
const stream = useStream({
apiUrl: "https://your-deployment.example",
assistantId: "agent",
});
const credentialAuth = computed(() => {
const value = stream.interrupt.value?.value;
return value?.type === "credential_authorization_required" ? value : null;
});
async function onComplete(connectedSlugs: string[]) {
await stream.respond(
{
type: "credential_authorization_completed",
connected_slugs: connectedSlugs,
},
{ interruptId: stream.interrupt.value?.id }
);
}
</script>
<template>
<div>
<!-- messages … -->
<ConnectCard
v-if="credentialAuth"
:payload="credentialAuth"
@complete="onComplete"
/>
</div>
</template>svelte
<script lang="ts">
import { useStream } from "@langchain/svelte";
const stream = useStream({
apiUrl: "https://your-deployment.example",
assistantId: "agent",
});
$: payload = stream.interrupt?.value;
$: credentialAuth =
payload?.type === "credential_authorization_required" ? payload : null;
async function onComplete(connectedSlugs: string[]) {
await stream.respond(
{
type: "credential_authorization_completed",
connected_slugs: connectedSlugs,
},
{ interruptId: stream.interrupt?.id }
);
}
</script>
<div>
<!-- messages … -->
{#if credentialAuth}
<ConnectCard payload={credentialAuth} onComplete={onComplete} />
{/if}
</div>ts
import { Component, computed } from "@angular/core";
import { injectStream } from "@langchain/angular";
@Component({
selector: "app-chat",
template: `
<!-- messages … -->
@if (credentialAuth(); as auth) {
<app-connect-card
[payload]="auth"
(complete)="onComplete($event)"
/>
}
`,
})
export class ChatComponent {
stream = injectStream({
apiUrl: "https://your-deployment.example",
assistantId: "agent",
});
credentialAuth = computed(() => {
const value = this.stream.interrupt()?.value;
return value?.type === "credential_authorization_required" ? value : null;
});
async onComplete(connectedSlugs: string[]) {
await this.stream.respond(
{
type: "credential_authorization_completed",
connected_slugs: connectedSlugs,
},
{ interruptId: this.stream.interrupt()?.id }
);
}
}关于一般的中断生命周期(stream.interrupt、恢复、检查点),见人在回路。
处理 OAuth 授权(kind: "oauth2")
对每个 OAuth 条目:
- 显示
slug和一个打开connect_url的 Connect 控件(仅 HTTPS;拒绝带内嵌凭据的 URL)。 - 长轮询 Agent Auth 直到会话完成:
http
GET /v1/agent-auth/oauth-authorization-sessions/{auth_id}?wait_seconds=25响应 status 为 pending、completed、failed 或 expired。当状态为 pending 时继续轮询。在 completed 时,把那个 slug 标记为已连接。在 failed 或 expired 时,请调用者发起一个新运行以获得一个新鲜的连接链接。
不要把访问令牌放进你的 UI。Agent Auth 为调用者存储授权;agent 在恢复时通过 connections.get(...) 读取它。
恢复运行
在 credentials 中每个条目都连接之后,用以下恢复:
json
{
"type": "credential_authorization_completed",
"connected_slugs": ["engineering-notion"]
}如上所示,把那个对象传给 stream.respond(resume, { interruptId: stream.interrupt?.id })。如果在一个授权仍缺失时恢复,关卡会带着一个新鲜载荷再次中断。
内置的 Slack 处理
当 agent 通过 Slack 频道运行时,Slack 会渲染带一个 HTTPS connect_url 的 OAuth 条目。调用者打开链接、完成同意,Slack 恢复运行,所以没人非得打开 LangSmith 来连接一个服务。
截图占位: 在此添加 Slack OAuth 授权提示的截图。
查看与删除连接
用 list 或 get 查看连接元数据。list 显示工作区里的每个连接,不只是本项目使用的那些:
bash
uv run mda connections list
uv run mda connections get organization-tavily
uv run mda connections delete organization-tavily向 list 或 get 传 --json 以获得机器可读输出。向 delete 传 --yes 以跳过确认提示。
本地开发
对于 mda dev,一个 agent 所有的不透明连接读 MDA_DEV_<SLUG>。CLI 把 slug 转大写并把连字符替换为下划线。例如,organization-tavily 读 MDA_DEV_ORGANIZATION_TAVILY。
用户所有的连接需要一个已身份验证的调用者和 Agent Auth。部署 agent 以端到端地演练授权中断。
查看当前限制
连接是 Managed Deep Agents 公测的一部分。以下缺口适用于当前版本:
- 用户所有的 API 密钥:一个用户所有的连接持有一个 OAuth 授权。Slack 和 Studio 不收集按调用者的 API 密钥,CLI 也不为其创建一个空槽。改用一个 agent 所有的机密,或如下一节所述在一个自定义前端里收集密钥。
- 自定义频道:Slack 和 Studio 解析调用者身份并为调用者完成授权。一个自定义前端自己处理授权中断。
- 工作区 UI:LangSmith 不在 UI 中列出连接。用
mda connections list和mda connections get查看连接元数据。 - 授权可见性:
mda connections list是工作区作用域的,且不报告哪些部署在某个 slug 下持有凭据。一个以no agent connection is set for slug '<slug>'失败的运行意味着该部署不拥有它的任何凭据,即使list显示那个 slug。从项目根运行mda connections create <slug>以铸造一个。
Store a user-owned API key through Agent Auth
一个带 "kind": "secret" 的中断条目没有 connect_url。要在一个自定义前端里收集一个,用一个隐藏输入提示填值,然后就现有连接 slug 创建凭据:
http
POST /v1/agent-auth/connectionsjson
{
"slug": "organization-tavily",
"display_name": "organization-tavily",
"credential": {
"kind": "secret",
"owner_type": "user",
"owner_id": "<caller-identity-id>",
"value": "<api-key>"
}
}Agent Auth 为那个 slug 复用现有连接并附加调用者的机密。机密材料是只写的。在一次成功创建之后,把那个 slug 标记为已连接并恢复运行。更多信息,见设置 Agent Auth。