Skip to content

Commit 67694d2

Browse files
Add connector registry for installed plugins
1 parent ff07f6e commit 67694d2

7 files changed

Lines changed: 495 additions & 21 deletions

File tree

docs/connectors.md

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,58 @@ separate runtime. A connector resolves to existing SDK primitives:
1010
Use connectors when you want to mount a named bundle of tools or package-provided MCP servers on
1111
one or more agents without manually copying every tool and server into each agent definition.
1212

13+
## Installed plugin registries
14+
15+
Use [`ConnectorRegistry`][agents.connectors.ConnectorRegistry] when your application already has
16+
installed plugin records from a Unified Plugins-style directory, marketplace, or workspace plugin
17+
service. The SDK does not fetch those records itself; the registry is an adapter over records that
18+
Codex, ChatGPT, your control plane, or tests have already provided.
19+
20+
A registry record can point at a mounted local package, hosted app connector IDs, or both. Loading
21+
the record still produces a normal [`Connector`][agents.connectors.Connector].
22+
23+
```python
24+
from agents import Agent, Connector, ConnectorRegistry
25+
26+
27+
registry = ConnectorRegistry.from_plugin_records(
28+
[
29+
{
30+
"id": "plugin_orders",
31+
"name": "orders",
32+
"package_path": "./orders-plugin",
33+
},
34+
{
35+
"id": "plugin_calendar",
36+
"name": "calendar",
37+
"apps": {
38+
"calendar": {
39+
"id": "connector_googlecalendar",
40+
}
41+
},
42+
},
43+
]
44+
)
45+
46+
orders = Connector.from_installed_plugin("plugin_orders", registry)
47+
calendar = Connector.from_installed_plugin(
48+
"plugin_calendar",
49+
registry,
50+
authorization={"calendar": "conn_calendar_access_token"},
51+
hosted_mcp_require_approval="always",
52+
)
53+
54+
agent = Agent(
55+
name="Assistant",
56+
instructions="Use installed connector plugins when needed.",
57+
connectors=[orders, calendar],
58+
)
59+
```
60+
61+
This keeps package discovery, marketplace installation, workspace sharing, admin policy, and cloud
62+
sync outside the SDK runtime while giving those systems a stable place to hand installed plugin
63+
records to the SDK.
64+
1365
## SDK tool connectors
1466

1567
Use [`Connector.from_tools()`][agents.connectors.Connector.from_tools] when your integration is
@@ -115,8 +167,9 @@ They are sent to the Responses API like other hosted tools.
115167
## End-to-end demo
116168

117169
See `examples/connectors/package_demo.py` for a deterministic demo that needs no API key. It builds
118-
a direct Python tool connector, creates a temporary plugin-style MCP package, mounts both on an
119-
agent, invokes the discovered tools, and inspects a hosted connector config.
170+
a direct Python tool connector, creates a temporary plugin-style MCP package, loads that package
171+
through a `ConnectorRegistry` record, mounts the resolved connector on an agent, invokes the
172+
discovered tools, and inspects a hosted connector config loaded from a registry record.
120173

121174
Run it with:
122175

docs/examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Check out a variety of sample implementations of the SDK in the examples section
4545
- **[connectors](https://github.com/openai/openai-agents-python/tree/main/examples/connectors):**
4646
Examples for packaging reusable connector surfaces, including:
4747

48-
- End-to-end connector package composition without an API key (`examples/connectors/package_demo.py`)
48+
- End-to-end connector registry and package composition without an API key (`examples/connectors/package_demo.py`)
4949

5050
- **[customer_service](https://github.com/openai/openai-agents-python/tree/main/examples/customer_service):**
5151
Example customer service system for an airline.

examples/connectors/package_demo.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@
88
from tempfile import TemporaryDirectory
99
from typing import Any
1010

11-
from agents import Agent, Connector, FunctionTool, HostedMCPTool, RunContextWrapper, function_tool
11+
from agents import (
12+
Agent,
13+
Connector,
14+
ConnectorRegistry,
15+
FunctionTool,
16+
HostedMCPTool,
17+
RunContextWrapper,
18+
function_tool,
19+
)
1220
from agents.mcp import MCPServerManager
1321
from agents.tool_context import ToolContext
1422

@@ -29,13 +37,25 @@ def build_sdk_tool_connector() -> Connector:
2937

3038

3139
def build_hosted_connector() -> Connector:
32-
return Connector.from_hosted_connector(
33-
"calendar",
34-
connector_id="connector_googlecalendar",
35-
authorization="demo_access_token",
36-
server_label="calendar",
37-
require_approval="never",
38-
description="Hosted Google Calendar connector shape.",
40+
registry = ConnectorRegistry.from_plugin_records(
41+
[
42+
{
43+
"id": "plugin_calendar",
44+
"name": "calendar",
45+
"description": "Hosted Google Calendar connector shape.",
46+
"apps": {
47+
"calendar": {
48+
"id": "connector_googlecalendar",
49+
}
50+
},
51+
}
52+
]
53+
)
54+
return Connector.from_installed_plugin(
55+
"plugin_calendar",
56+
registry,
57+
authorization={"calendar": "demo_access_token"},
58+
hosted_mcp_require_approval="never",
3959
)
4060

4161

@@ -94,7 +114,17 @@ def write_demo_plugin_package(package_root: Path) -> Path:
94114

95115

96116
def build_package_connector(package_root: Path) -> Connector:
97-
return Connector.from_package(package_root)
117+
registry = ConnectorRegistry.from_plugin_records(
118+
[
119+
{
120+
"id": "plugin_orders",
121+
"name": "orders",
122+
"package_path": str(package_root),
123+
"source": "unified_plugins_demo",
124+
}
125+
]
126+
)
127+
return Connector.from_installed_plugin("plugin_orders", registry)
98128

99129

100130
async def verify_connector_demo() -> dict[str, Any]:
@@ -144,6 +174,7 @@ async def verify_connector_demo() -> dict[str, Any]:
144174
"mcp_tool_result": mcp_tool_result,
145175
"package_connector_name": package_connector.name,
146176
"package_policy_labels": sorted(package_connector.policy_labels),
177+
"package_registry_source": package_connector.metadata["unified_plugin"]["source"],
147178
"hosted_connector_label": hosted_tool.tool_config["server_label"],
148179
"hosted_connector_id": hosted_tool.tool_config["connector_id"],
149180
}
@@ -191,7 +222,8 @@ def print_summary(summary: dict[str, Any]) -> None:
191222
print(
192223
"Package connector: "
193224
f"{summary['package_connector_name']} "
194-
f"({', '.join(summary['package_policy_labels'])})"
225+
f"({', '.join(summary['package_policy_labels'])}, "
226+
f"{summary['package_registry_source']})"
195227
)
196228
print(
197229
"Hosted connector config: "

src/agents/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
from .connectors import (
2020
Connector,
2121
ConnectorComponents,
22+
ConnectorPlugin,
2223
ConnectorPolicyLabel,
24+
ConnectorRegistry,
2325
HostedConnectorAuthorization,
2426
)
2527
from .editor import ApplyPatchEditor, ApplyPatchOperation, ApplyPatchResult
@@ -367,7 +369,9 @@ def enable_verbose_stdout_logging():
367369
"Button",
368370
"Connector",
369371
"ConnectorComponents",
372+
"ConnectorPlugin",
370373
"ConnectorPolicyLabel",
374+
"ConnectorRegistry",
371375
"HostedConnectorAuthorization",
372376
"AgentsException",
373377
"InputGuardrailTripwireTriggered",

0 commit comments

Comments
 (0)