From 0a6a7172c8cab301848c429b41797102dce4f001 Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Thu, 28 Aug 2025 12:11:52 +0900 Subject: [PATCH 1/2] Fix #374 add connector support --- examples/connectors/index.ts | 59 +++++++ examples/connectors/package.json | 12 ++ examples/connectors/tsconfig.json | 3 + packages/agents-core/src/tool.ts | 162 +++++++++++++----- .../agents-core/src/types/providerData.ts | 44 +++-- .../agents-openai/src/openaiResponsesModel.ts | 2 + pnpm-lock.yaml | 16 +- 7 files changed, 233 insertions(+), 65 deletions(-) create mode 100644 examples/connectors/index.ts create mode 100644 examples/connectors/package.json create mode 100644 examples/connectors/tsconfig.json diff --git a/examples/connectors/index.ts b/examples/connectors/index.ts new file mode 100644 index 00000000..32b1741d --- /dev/null +++ b/examples/connectors/index.ts @@ -0,0 +1,59 @@ +import { Agent, run, hostedMcpTool } from '@openai/agents'; + +async function main(verbose: boolean, stream: boolean): Promise { + // 1. Visit https://developers.google.com/oauthplayground/ + // 2. Input https://www.googleapis.com/auth/calendar.events as the required scope + // 3. Grab the acccess token starting with "ya29." + const authorization = process.env.GOOGLE_CALENDAR_AUTHORIZATION!; + + const agent = new Agent({ + name: 'My Calendar Assistant', + instructions: + 'You are a helpful assistant that can help a user with their calendar.', + tools: [ + hostedMcpTool({ + serverLabel: 'google_calendar', + connectorId: 'connector_googlecalendar', + authorization, + requireApproval: 'never', + }), + ], + }); + + const today = new Date().toISOString().split('T')[0]; + const input = `What is my schedule for ${today}?`; + if (stream) { + const result = await run(agent, input, { stream: true }); + for await (const event of result) { + if ( + event.type === 'raw_model_stream_event' && + event.data.type === 'model' && + event.data.event.type !== 'response.mcp_call_arguments.delta' && + event.data.event.type !== 'response.output_text.delta' + ) { + console.log(`Got event of type ${JSON.stringify(event.data)}`); + } + } + for (const item of result.newItems) { + console.log(JSON.stringify(item, null, 2)); + } + console.log(`Done streaming; final result: ${result.finalOutput}`); + } else { + const res = await run(agent, input); + if (verbose) { + for (const item of res.output) { + console.log(JSON.stringify(item, null, 2)); + } + } + console.log(res.finalOutput); + } +} + +const args = process.argv.slice(2); +const verbose = args.includes('--verbose'); +const stream = args.includes('--stream'); + +main(verbose, stream).catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/examples/connectors/package.json b/examples/connectors/package.json new file mode 100644 index 00000000..f3ca59e3 --- /dev/null +++ b/examples/connectors/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "name": "connectors", + "dependencies": { + "@openai/agents": "workspace:*", + "zod": "^3.25.40" + }, + "scripts": { + "build-check": "tsc --noEmit", + "start": "tsx index.ts" + } +} diff --git a/examples/connectors/tsconfig.json b/examples/connectors/tsconfig.json new file mode 100644 index 00000000..150a0961 --- /dev/null +++ b/examples/connectors/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.examples.json" +} diff --git a/packages/agents-core/src/tool.ts b/packages/agents-core/src/tool.ts index 9f8a0d21..01581480 100644 --- a/packages/agents-core/src/tool.ts +++ b/packages/agents-core/src/tool.ts @@ -134,52 +134,126 @@ export type HostedMCPTool = HostedTool & { */ export function hostedMcpTool( options: { - serverLabel: string; - serverUrl: string; allowedTools?: string[] | { toolNames?: string[] }; - headers?: Record; - } & ( - | { requireApproval?: never } - | { requireApproval: 'never' } - | { - requireApproval: - | 'always' - | { - never?: { toolNames: string[] }; - always?: { toolNames: string[] }; - }; - onApproval?: HostedMCPApprovalFunction; - } - ), -): HostedMCPTool { - const providerData: ProviderData.HostedMCPTool = - typeof options.requireApproval === 'undefined' || - options.requireApproval === 'never' - ? { - type: 'mcp', - server_label: options.serverLabel, - server_url: options.serverUrl, - require_approval: 'never', - allowed_tools: toMcpAllowedToolsFilter(options.allowedTools), - headers: options.headers, + } & + // MCP server + (| { + serverLabel: string; + serverUrl?: string; + authorization?: string; + headers?: Record; } - : { - type: 'mcp', - server_label: options.serverLabel, - server_url: options.serverUrl, - allowed_tools: toMcpAllowedToolsFilter(options.allowedTools), - headers: options.headers, - require_approval: - typeof options.requireApproval === 'string' - ? 'always' - : buildRequireApproval(options.requireApproval), - on_approval: options.onApproval, - }; - return { - type: 'hosted_tool', - name: 'hosted_mcp', - providerData, - }; + // OpenAI Connector + | { + serverLabel: string; + connectorId: string; + authorization?: string; + headers?: Record; + } + ) & + ( + | { requireApproval?: never } + | { requireApproval: 'never' } + | { + requireApproval: + | 'always' + | { + never?: { toolNames: string[] }; + always?: { toolNames: string[] }; + }; + onApproval?: HostedMCPApprovalFunction; + } + ), +): HostedMCPTool { + if ('serverUrl' in options) { + // the MCP servers comaptible with the specification + const providerData: ProviderData.HostedMCPTool = + typeof options.requireApproval === 'undefined' || + options.requireApproval === 'never' + ? { + type: 'mcp', + server_label: options.serverLabel, + server_url: options.serverUrl, + require_approval: 'never', + allowed_tools: toMcpAllowedToolsFilter(options.allowedTools), + headers: options.headers, + } + : { + type: 'mcp', + server_label: options.serverLabel, + server_url: options.serverUrl, + allowed_tools: toMcpAllowedToolsFilter(options.allowedTools), + headers: options.headers, + require_approval: + typeof options.requireApproval === 'string' + ? 'always' + : buildRequireApproval(options.requireApproval), + on_approval: options.onApproval, + }; + return { + type: 'hosted_tool', + name: 'hosted_mcp', + providerData, + }; + } else if ('connectorId' in options) { + // OpenAI's connectors + const providerData: ProviderData.HostedMCPTool = + typeof options.requireApproval === 'undefined' || + options.requireApproval === 'never' + ? { + type: 'mcp', + server_label: options.serverLabel, + connector_id: options.connectorId, + authorization: options.authorization, + require_approval: 'never', + allowed_tools: toMcpAllowedToolsFilter(options.allowedTools), + headers: options.headers, + } + : { + type: 'mcp', + server_label: options.serverLabel, + connector_id: options.connectorId, + authorization: options.authorization, + allowed_tools: toMcpAllowedToolsFilter(options.allowedTools), + headers: options.headers, + require_approval: + typeof options.requireApproval === 'string' + ? 'always' + : buildRequireApproval(options.requireApproval), + on_approval: options.onApproval, + }; + return { + type: 'hosted_tool', + name: 'hosted_mcp', + providerData, + }; + } else { + // the MCP servers comaptible with the specification + const providerData: ProviderData.HostedMCPTool = + typeof options.requireApproval === 'undefined' || + options.requireApproval === 'never' + ? { + type: 'mcp', + server_label: options.serverLabel, + require_approval: 'never', + allowed_tools: toMcpAllowedToolsFilter(options.allowedTools), + } + : { + type: 'mcp', + server_label: options.serverLabel, + allowed_tools: toMcpAllowedToolsFilter(options.allowedTools), + require_approval: + typeof options.requireApproval === 'string' + ? 'always' + : buildRequireApproval(options.requireApproval), + on_approval: options.onApproval, + }; + return { + type: 'hosted_tool', + name: 'hosted_mcp', + providerData, + }; + } } /** diff --git a/packages/agents-core/src/types/providerData.ts b/packages/agents-core/src/types/providerData.ts index 6cb37d8f..318d21bb 100644 --- a/packages/agents-core/src/types/providerData.ts +++ b/packages/agents-core/src/types/providerData.ts @@ -6,22 +6,35 @@ import { UnknownContext } from './aliases'; */ export type HostedMCPTool = { type: 'mcp'; - server_label: string; - server_url: string; allowed_tools?: string[] | { tool_names: string[] }; - headers?: Record; -} & ( - | { require_approval?: 'never'; on_approval?: never } - | { - require_approval: - | 'always' - | { - never?: { tool_names: string[] }; - always?: { tool_names: string[] }; - }; - on_approval?: HostedMCPApprovalFunction; - } -); +} & + // MCP server + (| { + server_label: string; + server_url?: string; + authorization?: string; + headers?: Record; + } + // OpenAI Connector + | { + server_label: string; + connector_id: string; + authorization?: string; + headers?: Record; + } + ) & + ( + | { require_approval?: 'never'; on_approval?: never } + | { + require_approval: + | 'always' + | { + never?: { tool_names: string[] }; + always?: { tool_names: string[] }; + }; + on_approval?: HostedMCPApprovalFunction; + } + ); export type HostedMCPListTools = { id: string; @@ -39,6 +52,7 @@ export type HostedMCPCall = { arguments: string; name: string; server_label: string; + connector_id?: string; error?: string | null; // excluding this large data field // output?: string | null; diff --git a/packages/agents-openai/src/openaiResponsesModel.ts b/packages/agents-openai/src/openaiResponsesModel.ts index e2173897..51efe541 100644 --- a/packages/agents-openai/src/openaiResponsesModel.ts +++ b/packages/agents-openai/src/openaiResponsesModel.ts @@ -194,6 +194,8 @@ function converTool<_TContext = unknown>( type: 'mcp', server_label: tool.providerData.server_label, server_url: tool.providerData.server_url, + connector_id: tool.providerData.connector_id, + authorization: tool.providerData.authorization, allowed_tools: tool.providerData.allowed_tools, headers: tool.providerData.headers, require_approval: convertMCPRequireApproval( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd45321b..7db3fb6a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -169,6 +169,15 @@ importers: specifier: ^3.25.40 version: 3.25.62 + examples/connectors: + dependencies: + '@openai/agents': + specifier: workspace:* + version: link:../../packages/agents + zod: + specifier: ^3.25.40 + version: 3.25.62 + examples/customer-service: dependencies: '@openai/agents': @@ -6341,9 +6350,6 @@ packages: zod@3.25.62: resolution: {integrity: sha512-YCxsr4DmhPcrKPC9R1oBHQNlQzlJEyPAId//qTau/vBee9uO8K6prmRq4eMkOyxvBfH4wDPIPdLx9HVMWIY3xA==} - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -6520,7 +6526,7 @@ snapshots: dependencies: sitemap: 8.0.0 stream-replace-string: 2.0.0 - zod: 3.25.76 + zod: 3.25.62 '@astrojs/starlight-tailwind@4.0.1(@astrojs/starlight@0.35.2(astro@5.13.4(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.44.2)(tsx@4.20.1)(typescript@5.8.3)(yaml@2.7.1)))(tailwindcss@3.4.17)': dependencies: @@ -12878,6 +12884,4 @@ snapshots: zod@3.25.62: {} - zod@3.25.76: {} - zwitch@2.0.4: {} From 47b73187d6302e511e27973c6de2e1fc40a6d053 Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Thu, 28 Aug 2025 14:57:47 +0900 Subject: [PATCH 2/2] Create tiny-maps-begin.md --- .changeset/tiny-maps-begin.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/tiny-maps-begin.md diff --git a/.changeset/tiny-maps-begin.md b/.changeset/tiny-maps-begin.md new file mode 100644 index 00000000..58e3af7a --- /dev/null +++ b/.changeset/tiny-maps-begin.md @@ -0,0 +1,6 @@ +--- +"@openai/agents-core": patch +"@openai/agents-openai": patch +--- + +Fix #374 add connector support