LangChain 为 Managed Deep Agents 推出 Connections 功能,用于安全托管凭据、支持按用户 OAuth,并让智能体以每个调用方的身份执行操作。
Managed Deep Agents v0.7.0 及以上版本现在可用连接功能。
每个代理最终都需要代表某人采取行动——搜索网页、提交工单、打开拉取请求。今天,这通常意味着在每个部署中硬编码同一个 API 密钥,并且每个操作都显示在服务账户下。.env 中的密钥回答了代理可以做什么,但无法回答是谁发起的。
这就是 Connections 的作用。连接是 LangSmith 工作区中的一个命名凭证,您的工具在运行时可以通过 slug 通过一次调用读取它。
一个连接有所有者和凭证类型,它们是独立的。
所有者可以是代理或调用者。代理拥有的凭证属于部署,每个调用者共享它。用户拥有的凭证在运行时按人进行解析。
凭证可以是静态密钥或 OAuth 授权:代理可以持有 OAuth 授权,用户可以持有密钥。
在使用 mda connections create 创建连接时所有权已经固定,而 connections.get() 只会在已经存在的凭证中进行选择。
代理拥有的密钥用于需要每个调用者共享一个凭证的情况。这是适用于每个人都不需要不同的功能的正确方法:网页搜索、地理编码器、定价源。
在这个例子中,让我们配置一个到 Tavily:https://www.tavily.com 的连接,为代理添加一个通用网页搜索工具:
uv run mda connections create tavily-agent --secret-from-env TAVILY_API_KEY
tavily-agent 是 slug。这是您为连接命名的名字,也是您的代码使用的名字,没有任何检查会将其与供应商列表对比。值来自 TAVILY_API_KEY 并进入您的 LangSmith 工作区。它不是构建的一部分,mda deploy 也不会像处理 .env 进入部署密钥那样扫入它。
读取它的工具是普通的 LangChain 工具,只需一行新代码即可利用 connections.get:
# tools/search_web.py import httpx from langchain.tools import tool from managed_deepagents import connections @tool(parse_docstring=True) async def search_web(query: str) -> str: """ 搜索网页。 参数: query: 搜索查询。 """ api_key = await connections.get("tavily-agent", {"type": "agent"}) async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( " ", json={"api_key": api_key, "query": query, "max_results": 5}, ) response.raise_for_status() return response.text
如果你需要更换你的密钥,你可以更新存储在 tavily-agent 的秘钥,任何未来的代理请求将自动使用新的密钥。
共享令牌很有用,但允许你的代理代表你的用户操作意味着你可以安全地为你的代理提供更多功能。GitHub 在 connections 目录中与其他 22 个服务一起提供,因此你只需提供客户端 ID 和秘钥,而无需其他内容——无需授权 URL、无令牌 URL、无需查找认证方法。
你可以通过 mda connections catalog 快速引用目录连接,但如果你提供自己的元数据,你可以连接到任何提供 OAuth 的供应商。
例如,要配置一个自定义 Github OAuth 应用的连接:
uv run mda connections create github-issues \ --oAuth GitHub \ --client-ID “$GITHUB_CLIENT_ID” \ --Secret-from-env GITHUB_CLIENT_SECRET \ --scope repo
在此示例中,github-issues 是 slug,由你拥有,并由你的代码使用。github 是目录服务,它仅决定哪些端点被填充。
工具通过一个助手读取令牌。在此示例中,关键行是:
access_token = await connections.get("github-issues", {"type": "user"})
通过一次对 connections.get 的调用,部署的代理可以自动为新用户调用 OAuth 流程,或者获取已对 OAuth 提供者进行过身份验证的用户的缓存 OAuth 令牌。
我们可以利用此访问令牌对 Github 进行任意 API 调用:
# tools/github.py async def _github(method: str, path: str, **kwargs) -> dict: access_token = await connections.get("github-issues", {"type": "user"}) async with httpx.AsyncClient(timeout=30.0) as client: response = await client.request( method, f"{GITHUB_API}{path}", headers={ "Authorization": f"Bearer {access_token}", "Accept": "application/vnd.github+json", "X-GitHub-Api-Version": GITHUB_VERSION, }, **kwargs, ) response.raise_for_status() return response.json()
注意我们设置了 {"type": "user"}。代理拥有的连接在创建时存储了一个值。而这个完全没有存储任何值,只有应用注册。凭证是在运行时、按调用者到达的——如果调用者从未授权 GitHub,或者他们的令牌已过期,connections.get() 会暂停运行并请求授权,而不是失败。
该词只出现一次,在 _github 内。search_issues 工具和 create_issue 工具都从该辅助函数继承按调用者的身份,第三个 GitHub 工具根本不需要任何授权代码。
好处体现在两个地方。search_issues 在任何写入操作之前已经因调用者不同而结果不同,因为一个人可见的私有仓库,另一个人不可见会改变结果——相同查询,相同部署,不同答案。当 create_issue 运行时,问题会由提出请求的人在 GitHub 上创建。响应中的 user.login 是他们的账号,而不是机器人。
一些 MCP 服务器会自己注册 OAuth 客户端。它们这么做时,整个设置就是一个 URL。
uv 运行 mda 连接 创建 linear-mcp --mcp
from managed_deepagents import connections, define_mcp mcp = define_mcp( servers={ "linear": { "transport": "http", "url": " ", "connection": connections.get("linear-mcp", {"type": "user"}), }, }, )
没有客户端 ID,没有客户端密钥,没有应用注册。因为服务器会发布其 OAuth 元数据并为你注册一个客户端,你也不需要作用域,并且连接自带读写权限,这些都是通过服务器自身元数据协商得出的。
将其与 GitHub 流程进行比较:一个需要你自己的应用程序,一个什么也不需要,而读取它们的代码行是相同的。这里消失的是工具代码——GitHub 使用了一个助手和两个函数,而这里只需一个服务器 URL,工具从 MCP 服务器获取。
向代理请求跨两个服务的内容,运行会在第一次模型回合前暂停,并显示一个中断,列出所有调用者尚未授权的连接。授权它们后,运行会从停止的地方继续。
项目中没有回调路由,没有令牌存储,没有刷新逻辑,没有同意屏幕。调用者从不打开 LangSmith。
作为第二个调用者做同样的事,你会得到第二个问题,其作者不同,来自同一代理,相同的 slug,以及相同的工作区条目。与 Tavily 密钥形成对比,Tavily 密钥设计上对每个人都是一样的。你可以通过以下方式检查你或其他开发者添加到 LangSmith 的连接:
连接包含在 Managed Deep Agents 预发布版本中,而 OAuth 目录包含在二进制文件中,因此你所拥有的版本决定了 --oauth 接受什么:
uv 工具 安装 managed-deepagents uv 运行 mda 连接 目录
代理拥有的凭证归部署所有,因此在创建之前先进行一次脚手架搭建和部署。之后,每个连接有三个步骤——创建它,用 connections.get() 读取它,然后重新部署以发送读取它的代码。
本地开发方式相同。代理拥有的连接从 .env 文件中的 MDA_DEV_ 中解析,大写并将连字符变为下划线。用户拥有的连接将登录的开发者解析为 mda dev 下的真实主体,因此授权中断会在本地触发,并且存储的授权是真实的。
除了上述三种流程,--authorize 为部署存储一个 OAuth 授权,因此每个调用者都充当单一共享账户——这是按凭证拥有者模型的第四格,也是当你想要专用团队账户而不是每人身份时的正确做法。--allowed-scope 限制后续授权可能请求的范围,而 --authorize-url 配合 --token-url 覆盖目录外的任何提供者。
有关详细信息和示例,Connections 的文档可以在 https://docs.langchain.com/langsmith/python/managed-deep-agents-connections 找到:https://docs.langchain.com/langsmith/python/managed-deep-agents-connections
LangSmith,我们的代理工程平台,帮助开发者调试每一个代理决策,评估变更,并一键部署。
Connections are available now in Managed Deep Agents v0.7.0+.
Every agent eventually needs to act on someone's behalf — search the web, file a ticket, open a pull request. Today that usually means one API key hard-coded across every deployment, and every action showing up under a service account. A key in .env answers what the agent may do. It has no way to answer who asked.
That is what Connections fixes. A connection is a named credential in your LangSmith workspace that your tools read at run time, by slug, through one call.
A connection has an owner and a credential type, and they are independent.
The owner is either the agent or the caller. An agent-owned credential belongs to the deployment, and every caller shares it. A user-owned credential resolves per person, at run time.
The credential is either a static secret or an OAuth grant: an agent can hold an OAuth grant, and a user can hold a secret.
Ownership is fixed when you create the connection using mda connections create , and connections.get() only selects among credentials that already exist.
An agent-owned secret is used in situations where you need one credential shared by every caller. This is the right approach for a capability that does not differ per person: web search, a geocoder, a pricing feed.
In this example, let’s configure a connection to Tavily:https://www.tavily.com to add a generic web search tool to an agent:
uv run mda connections create tavily-agent --secret-from-env TAVILY_API_KEY
tavily-agent is the slug. It is your name for the connection and the name your code uses, and nothing checks it against a provider list. The value came out of TAVILY_API_KEY and went into your LangSmith workspace. It is not part of the build, and mda deploy does not sweep it in the way it sweeps .env into deployment secrets.
The tool that reads it is an ordinary LangChain tool with one new line leveraging connections.get :
# tools/search_web.py 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("tavily-agent", {"type": "agent"}) async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( " ", json={"api_key": api_key, "query": query, "max_results": 5}, ) response.raise_for_status() return response.text
If you need to rotate your key, you can update the secret stored at tavily-agent , and any future agent requests will automatically use the new key.
Shared tokens are useful, but allowing your agent to act on behalf of your users means you can securely provide more capabilities to your agent. GitHub ships in the connections catalog alongside 22 other services, so you bring a client ID and a secret and nothing else — no authorization URL, no token URL, no auth method to look up.
You can quickly reference the catalog connections with mda connections catalog , but you can connect to any provider which offers OAuth if you bring your own metadata.
For example, to configure a connection to a custom Github OAuth app:
uv run mda connections create github-issues \ --oauth github \ --client-id "$GITHUB_CLIENT_ID" \ --secret-from-env GITHUB_CLIENT_SECRET \ --scope repo
In this example, github-issues is the slug, which is yours and which your code uses. github is the catalog service, which only decides which endpoints get filled in.
The tools read the token through a helper. In this example, the key line is:
access_token = await connections.get("github-issues", {"type": "user"})
With a single call to connections.get , a deployed agent can automatically either invoke an OAuth flow for a new user or fetch a cached OAuth token for a user who has previously authenticated against the OAuth provider.
We can leverage this access token to make arbitrary API calls to Github:
# tools/github.py async def _github(method: str, path: str, **kwargs) -> dict: access_token = await connections.get("github-issues", {"type": "user"}) async with httpx.AsyncClient(timeout=30.0) as client: response = await client.request( method, f"{GITHUB_API}{path}", headers={ "Authorization": f"Bearer {access_token}", "Accept": "application/vnd.github+json", "X-GitHub-Api-Version": GITHUB_VERSION, }, **kwargs, ) response.raise_for_status() return response.json()
Notice that we set {"type": "user"} . The agent-owned connection stored a value at create time. This one stored no value at all, only the app registration. The credential arrives per caller, at run time — and if the caller has never authorized GitHub, or their token has expired, connections.get() pauses the run and asks for a grant instead of failing.
That word appears once, inside _github . A search_issues tool and a create_issue tool both inherit per-caller identity from the helper, and a third GitHub tool would cost no auth code at all.
The payoff shows up in two places. search_issues already differs per caller before anything is written, because private repositories one person can see and another cannot change the results — same query, same deployment, different answers. And when create_issue runs, the issue lands in GitHub opened by the person who asked. user.login in the response is their handle, not a bot's.
Some MCP servers register the OAuth client themselves. When they do, the whole setup is a URL.
uv run mda connections create linear-mcp --mcp
from managed_deepagents import connections, define_mcp mcp = define_mcp( servers={ "linear": { "transport": "http", "url": " ", "connection": connections.get("linear-mcp", {"type": "user"}), }, }, )
No client ID, no client secret, no app registration. Because the server advertises its OAuth metadata and a client gets registered for you, you don’t need a scope either, and the connection came out with read and write on it, negotiated from the server's own metadata.
Compare that with the GitHub flow: one needed your own app and one needed nothing, and the line of code reading them is the same. The tool code is the part that disappears here — GitHub took a helper and two functions, this takes a server URL, and the tools arrive from the MCP server.
Ask an agent for something that spans both services and the run pauses before the first model turn, with a single interrupt listing every connection the caller has not granted. Authorize them and the run resumes where it stopped.
There is no callback route in the project, no token store, no refresh logic, no consent screen. The caller never opens LangSmith.
Do the same thing as a second caller and you get a second issue with a different author, from the same agent, the same slug, and the same workspace entry. Contrast that with the Tavily key, which is the same for everyone by design. You can check the connections you or other developers added to LangSmith with:
Connections ship in the Managed Deep Agents prerelease, and the OAuth catalog ships inside the binary, so the version you have decides what --oauth accepts:
uv tool install managed-deepagents uv run mda connections catalog
An agent-owned credential belongs to a deployment, so scaffold and deploy once before creating one. After that, each connection is three steps — create it, read it with connections.get() , redeploy to ship the code that reads it.
Local development works the same way. Agent-owned connections resolve from MDA_DEV_ in .env , uppercased with hyphens as underscores. User-owned connections resolve the signed-in developer to a real principal under mda dev , so the authorization interrupt fires locally and the grant it stores is a real one.
Beyond the three flows above, --authorize stores one OAuth grant for the deployment, so every caller acts as a single shared account — the fourth cell of the owner-by-credential model, and the right answer when you want a dedicated team account rather than per-person identity. --allowed-scope caps what later authorizations may ask for, and --authorize-url with --token-url covers any provider outside the catalog.
For more detail and examples, documentation for Connections can be found at https://docs.langchain.com/langsmith/python/managed-deep-agents-connections:https://docs.langchain.com/langsmith/python/managed-deep-agents-connections
LangSmith, our agent engineering platform, helps developers debug every agent decision, eval changes, and deploy in one click.