Puedes agregar herramientas a una sesión de Realtime para que el modelo consulte datos, realice acciones o llame a servicios durante una conversación en vivo. Los eventos para configurar las herramientas son los mismos tanto si tu cliente usa un canal de datos WebRTC como si usa un WebSocket.
Usa herramientas de tipo función cuando tu aplicación deba ejecutar la herramienta y devolver el resultado. Usa herramientas MCP cuando quieras que la Realtime API se conecte a un servidor de herramientas remoto por ti.
| Tipo de herramienta |
Cuándo usarla |
Quién la ejecuta |
function |
Tu aplicación se encarga de la lógica de negocio, las verificaciones de aprobación o el acceso a sistemas privados. |
Tu cliente o servidor recibe una llamada a una función y devuelve function_call_output. |
mcp con server_url |
Quieres que el modelo llame a las herramientas que ofrece un servidor MCP remoto. |
La Realtime API llama al servidor MCP remoto. |
mcp con connector_id |
Usas un conector integrado heredado con un modelo existente. |
La Realtime API llama al conector con la autorización que proporcionas. |
Agrega las herramientas en uno de estos dos niveles:
- A nivel de sesión, con
session.tools en session.update, si quieres que la herramienta esté disponible durante toda la sesión.
- A nivel de respuesta, con
response.tools en response.create, si solo necesitas la herramienta para un turno.
Si la herramienta debe ejecutarse en tu aplicación, elige una herramienta de tipo función como opción predeterminada. El modelo genera los argumentos de la llamada a la función. Tu código ejecuta la acción y devuelve el resultado mediante un elemento function_call_output.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27const event = {
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-2.1",
tools: [
{
type: "function",
name: "lookup_order",
description: "Look up an order by its order number.",
parameters: {
type: "object",
properties: {
order_number: {
type: "string",
description: "The customer-facing order number.",
},
},
required: ["order_number"],
},
},
],
tool_choice: "auto",
},
};
ws.send(JSON.stringify(event));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27event = {
"type": "session.update",
"session": {
"type": "realtime",
"model": "gpt-realtime-2.1",
"tools": [
{
"type": "function",
"name": "lookup_order",
"description": "Look up an order by its order number.",
"parameters": {
"type": "object",
"properties": {
"order_number": {
"type": "string",
"description": "The customer-facing order number.",
}
},
"required": ["order_number"],
},
}
],
"tool_choice": "auto",
},
}
ws.send(json.dumps(event))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27if err := conn.Send(ctx, realtime.RealtimeClientEventParamOfSessionUpdate(realtime.RealtimeSessionCreateRequestParam{
Model: "gpt-realtime-2.1",
ToolChoice: realtime.RealtimeToolChoiceConfigUnionParam{
OfToolChoiceMode: param.NewOpt[responses.ToolChoiceOptions]("auto"),
},
Tools: realtime.RealtimeToolsConfigParam{
realtime.RealtimeToolsConfigUnionParam{
OfFunction: &realtime.RealtimeFunctionToolParam{
Description: openai.String("Look up an order by its order number."),
Name: openai.String("lookup_order"),
Parameters: map[string]any{
"properties": map[string]any{
"order_number": map[string]any{
"description": "The customer-facing order number.",
"type": "string",
},
},
"required": []any{"order_number"},
"type": "object",
},
Type: "function",
},
},
},
})); err != nil {
return err
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.core.JsonValue;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
import java.util.List;
import java.util.Map;
connection.send(
RealtimeClientEvent.ofSessionUpdate(
SessionUpdateEvent.builder()
.session(
RealtimeSessionCreateRequest.builder()
.model("gpt-realtime-2.1")
.addTool(
RealtimeFunctionTool.builder()
.type(RealtimeFunctionTool.Type.FUNCTION)
.name("lookup_order")
.description("Look up an order by its order number.")
.parameters(
JsonValue.from(
Map.of(
"type",
"object",
"properties",
Map.of(
"order_number",
Map.of(
"type",
"string",
"description",
"The customer-facing order number.")),
"required",
List.of("order_number"))))
.build())
.toolChoice(com.openai.models.responses.ToolChoiceOptions.AUTO)
.build())
.build()));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17using OpenAI.Realtime;
#pragma warning disable OPENAI002
await session.SendCommandAsync(new RealtimeClientCommandSessionUpdate(new RealtimeConversationSessionOptions
{
Model = "gpt-realtime-2.1",
Tools =
{
new RealtimeFunctionTool("lookup_order")
{
FunctionDescription = "Look up an order by its order number.",
FunctionParameters = BinaryData.FromObjectAsJson(new { type = "object", properties = new { order_number = new { type = "string", description = "The customer-facing order number." } }, required = (string[])["order_number"] })
}
},
ToolChoice = RealtimeDefaultToolChoice.Auto
}), timeout.Token);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29connection.session.update(
type: :realtime,
model: "gpt-realtime-2.1",
tools: [
{
type: :function,
name: "lookup_order",
description: "Look up an order by its order number.",
parameters: {
type: "object",
properties: {
order_number: {
type: "string",
description: "The customer-facing order number."
}
},
required: ["order_number"]
}
}
],
tool_choice: :auto
)
updated = connection.find do |event|
raise event.error.message if event.is_a?(OpenAI::Realtime::RealtimeErrorEvent)
event.is_a?(OpenAI::Realtime::SessionUpdatedEvent)
end
raise "Connection closed before the session update was acknowledged" unless updated
Cuando el modelo llame a la función, espera a recibir el elemento correspondiente a la llamada, ejecuta la lógica de tu aplicación y devuelve el resultado:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const event = {
type: "conversation.item.create",
item: {
type: "function_call_output",
call_id: functionCall.call_id,
output: JSON.stringify({
status: "shipped",
delivery_date: "2026-05-09",
}),
},
};
ws.send(JSON.stringify(event));
ws.send(
JSON.stringify({
type: "response.create",
response: { output_modalities: ["text"], tool_choice: "none" },
})
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23event = {
"type": "conversation.item.create",
"item": {
"type": "function_call_output",
"call_id": function_call["call_id"],
"output": json.dumps(
{
"status": "shipped",
"delivery_date": "2026-05-09",
}
),
},
}
ws.send(json.dumps(event))
ws.send(
json.dumps(
{
"type": "response.create",
"response": {"output_modalities": ["text"], "tool_choice": "none"},
}
)
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// callID comes from the model's function_call item.
if err := conn.Send(ctx, realtime.RealtimeClientEventParamOfConversationItemCreate(realtime.RealtimeConversationItemFunctionCallOutputParam{
CallID: callID,
Output: "{\"status\":\"shipped\",\"delivery_date\":\"2026-05-09\"}",
})); err != nil {
return err
}
if err := conn.Send(ctx, realtime.RealtimeClientEventUnionParam{
OfResponseCreate: &realtime.ResponseCreateEventParam{
Response: realtime.RealtimeResponseCreateParams{
OutputModalities: []string{"text"},
ToolChoice: realtime.RealtimeResponseCreateParamsToolChoiceUnion{
OfToolChoiceMode: param.NewOpt[responses.ToolChoiceOptions]("none"),
},
},
},
}); err != nil {
return err
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.core.JsonMissing;
import com.openai.core.JsonValue;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
import com.openai.models.responses.ToolChoiceFunction;
import com.openai.models.responses.ToolChoiceOptions;
import java.util.Map;
static void sendFunctionCallOutput(RealtimeConnection connection, String callId)
throws Exception {
send(
connection,
RealtimeClientEvent.ofConversationItemCreate(
ConversationItemCreateEvent.builder()
.item(
RealtimeConversationItemFunctionCallOutput.builder()
.object_(JsonMissing.of())
.callId(callId)
.output("{\"status\":\"shipped\",\"delivery_date\":\"2026-05-09\"}")
.build())
.build()));
send(
connection,
RealtimeClientEvent.ofResponseCreate(
ResponseCreateEvent.builder()
.response(
RealtimeResponseCreateParams.builder()
.metadata(
RealtimeResponseCreateParams.Metadata.builder()
.putAdditionalProperty(
"topic", JsonValue.from("lookup_order_followup"))
.build())
.addOutputModality(RealtimeResponseCreateParams.OutputModality.TEXT)
.toolChoice(ToolChoiceOptions.NONE)
.build())
.build()));
}
// Retry only explicit admission rejections; these guarantee nothing was sent.
private static void send(RealtimeConnection connection, RealtimeClientEvent event)
throws Exception {
long deadline = System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(5);
while (true) {
try {
connection.send(event);
return;
} catch (com.openai.core.http.WebSocketWriteNotAttempted.Busy busy) {
if (System.nanoTime() >= deadline) throw busy;
Thread.sleep(10);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18using OpenAI.Realtime;
#pragma warning disable OPENAI002
internal static async Task SendFunctionCallOutputAsync(RealtimeSessionClient session, string callId, CancellationToken cancellationToken)
{
string output = System.Text.Json.JsonSerializer.Serialize(new { status = "shipped", delivery_date = "2026-05-09" });
await session.SendCommandAsync(new RealtimeClientCommandConversationItemCreate(new RealtimeFunctionCallOutputItem(callId, output)), cancellationToken);
await session.SendCommandAsync(new RealtimeClientCommandResponseCreate
{
ResponseOptions = new()
{
OutputModalities = { RealtimeOutputModality.Text },
Metadata = new Dictionary<string, BinaryData> { ["topic"] = BinaryData.FromObjectAsJson("lookup_order_followup") },
ToolChoice = RealtimeDefaultToolChoice.None
}
}, cancellationToken);
}
1
2
3
4
5
6connection.conversation.items.create(
type: :function_call_output,
call_id: call_id,
output: JSON.generate(status: "shipped", delivery_date: "2026-05-09")
)
connection.response.create(output_modalities: [:text], tool_choice: :none)
Para ver cómo funciona la llamada a funciones, evento por evento, consulta Gestión de conversaciones.
Las herramientas MCP son útiles cuando un servidor MCP remoto ya ofrece la herramienta o cuando un modelo existente usa un conector integrado heredado. A diferencia de las herramientas de tipo función, las herramientas MCP las ejecuta la propia Realtime API.
En Realtime, una herramienta MCP tiene la siguiente estructura:
type: "mcp"
server_label
- Uno de estos dos campos:
server_url o connector_id
authorization y headers (opcionales)
allowed_tools (opcional)
require_approval (opcional)
server_description (opcional)
En este ejemplo, un servidor MCP de documentación queda disponible durante toda la sesión:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const event = {
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-2.1",
output_modalities: ["text"],
tools: [
{
type: "mcp",
server_label: "openai_docs",
server_url: "https://developers.openai.com/mcp",
allowed_tools: ["search_openai_docs", "fetch_openai_doc"],
require_approval: "never",
},
],
},
};
ws.send(JSON.stringify(event));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19event = {
"type": "session.update",
"session": {
"type": "realtime",
"model": "gpt-realtime-2.1",
"output_modalities": ["text"],
"tools": [
{
"type": "mcp",
"server_label": "openai_docs",
"server_url": "https://developers.openai.com/mcp",
"allowed_tools": ["search_openai_docs", "fetch_openai_doc"],
"require_approval": "never",
}
],
},
}
ws.send(json.dumps(event))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20if err := conn.Send(ctx, realtime.RealtimeClientEventParamOfSessionUpdate(realtime.RealtimeSessionCreateRequestParam{
Model: "gpt-realtime-2.1",
OutputModalities: []string{"text"},
Tools: realtime.RealtimeToolsConfigParam{
{
OfMcp: &realtime.RealtimeToolsConfigUnionMcpParam{
ServerLabel: "openai_docs",
ServerURL: openai.String("https://developers.openai.com/mcp"),
AllowedTools: realtime.RealtimeToolsConfigUnionMcpAllowedToolsParam{
OfMcpAllowedTools: []string{"search_openai_docs", "fetch_openai_doc"},
},
RequireApproval: realtime.RealtimeToolsConfigUnionMcpRequireApprovalParam{
OfMcpToolApprovalSetting: openai.String("never"),
},
},
},
},
})); err != nil {
return err
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
import java.util.List;
connection.send(
RealtimeClientEvent.ofSessionUpdate(
SessionUpdateEvent.builder()
.session(
RealtimeSessionCreateRequest.builder()
.model("gpt-realtime-2.1")
.addOutputModality(RealtimeSessionCreateRequest.OutputModality.TEXT)
.addTool(
RealtimeToolsConfigUnion.Mcp.builder()
.serverLabel("openai_docs")
.serverUrl("https://developers.openai.com/mcp")
.allowedToolsOfMcp(
List.of("search_openai_docs", "fetch_openai_doc"))
.requireApproval(
RealtimeToolsConfigUnion.Mcp.RequireApproval
.McpToolApprovalSetting.NEVER)
.build())
.build())
.build()));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27using OpenAI.Realtime;
#pragma warning disable OPENAI002
await session.SendCommandAsync(new RealtimeClientCommandSessionUpdate(new RealtimeConversationSessionOptions
{
Model = "gpt-realtime-2.1",
OutputModalities =
{
RealtimeOutputModality.Text
},
Tools =
{
new RealtimeMcpTool("openai_docs", new Uri("https://developers.openai.com/mcp"))
{
AllowedTools = new()
{
ToolNames =
{
"search_openai_docs",
"fetch_openai_doc"
}
},
ToolCallApprovalPolicy = RealtimeDefaultMcpToolCallApprovalPolicy.NeverRequireApproval
}
}
}), timeout.Token);
1
2
3
4
5
6
7
8
9
10
11
12
13
14connection.session.update(
type: :realtime,
model: "gpt-realtime-2.1",
output_modalities: [:text],
tools: [
{
type: :mcp,
server_label: "openai_docs",
server_url: "https://developers.openai.com/mcp",
allowed_tools: ["search_openai_docs", "fetch_openai_doc"],
require_approval: :never
}
]
)
connector_id está en desuso para los modelos lanzados después del 1 de septiembre de 2026. Usa server_url para conectarte a un servidor MCP remoto o
tunnel_id para conectarte a un servidor MCP local mediante un
túnel MCP seguro. Los modelos existentes siguen admitiendo conectores. El siguiente ejemplo usa gpt-realtime-1.5, que se lanzó antes de esa fecha.
Los conectores integrados usan la misma estructura que las demás herramientas MCP, pero debes pasar connector_id en lugar de server_url. Por ejemplo, Google Calendar usa connector_googlecalendar. En Realtime, usa estos conectores integrados para operaciones de lectura, como buscar o leer eventos o correos electrónicos. Pasa el token de acceso OAuth del usuario en authorization y, cuando sea posible, limita las herramientas disponibles con allowed_tools:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20const event = {
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-1.5",
output_modalities: ["text"],
tools: [
{
type: "mcp",
server_label: "google_calendar",
connector_id: "connector_googlecalendar",
authorization: "<google-oauth-access-token>",
allowed_tools: ["search_events", "read_event"],
require_approval: "never",
},
],
},
};
ws.send(JSON.stringify(event));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import os
connector_authorization = os.environ["OPENAI_CONNECTOR_AUTHORIZATION"]
event = {
"type": "session.update",
"session": {
"type": "realtime",
"model": "gpt-realtime-1.5",
"output_modalities": ["text"],
"tools": [
{
"type": "mcp",
"server_label": "google_calendar",
"connector_id": "connector_googlecalendar",
"authorization": connector_authorization,
"allowed_tools": ["search_events", "read_event"],
"require_approval": "never",
}
],
},
}
ws.send(json.dumps(event))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26accessToken := os.Getenv("OPENAI_MCP_ACCESS_TOKEN")
if accessToken == "" {
return fmt.Errorf("OPENAI_MCP_ACCESS_TOKEN is required")
}
if err := conn.Send(ctx, realtime.RealtimeClientEventParamOfSessionUpdate(realtime.RealtimeSessionCreateRequestParam{
Model: "gpt-realtime-1.5",
OutputModalities: []string{"text"},
Tools: realtime.RealtimeToolsConfigParam{
{
OfMcp: &realtime.RealtimeToolsConfigUnionMcpParam{
ServerLabel: "google_calendar",
ConnectorID: "connector_googlecalendar",
Authorization: param.NewOpt(accessToken),
AllowedTools: realtime.RealtimeToolsConfigUnionMcpAllowedToolsParam{
OfMcpAllowedTools: []string{"search_events", "read_event"},
},
RequireApproval: realtime.RealtimeToolsConfigUnionMcpRequireApprovalParam{
OfMcpToolApprovalSetting: openai.String("never"),
},
},
},
},
})); err != nil {
return err
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
import java.util.List;
connection.send(
RealtimeClientEvent.ofSessionUpdate(
SessionUpdateEvent.builder()
.session(
RealtimeSessionCreateRequest.builder()
.model("gpt-realtime-1.5")
.addOutputModality(RealtimeSessionCreateRequest.OutputModality.TEXT)
.addTool(
RealtimeToolsConfigUnion.Mcp.builder()
.serverLabel("google_calendar")
.connectorId(
RealtimeToolsConfigUnion.Mcp.ConnectorId
.CONNECTOR_GOOGLECALENDAR)
.authorization(System.getenv("OPENAI_CONNECTOR_AUTHORIZATION"))
.allowedToolsOfMcp(List.of("search_events", "read_event"))
.requireApproval(
RealtimeToolsConfigUnion.Mcp.RequireApproval
.McpToolApprovalSetting.NEVER)
.build())
.build())
.build()));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28using OpenAI.Realtime;
#pragma warning disable OPENAI002
await session.SendCommandAsync(new RealtimeClientCommandSessionUpdate(new RealtimeConversationSessionOptions
{
Model = "gpt-realtime-1.5",
OutputModalities =
{
RealtimeOutputModality.Text
},
Tools =
{
new RealtimeMcpTool("google_calendar", RealtimeMcpToolConnectorId.GoogleCalendar)
{
AuthorizationToken = Environment.GetEnvironmentVariable("OPENAI_CONNECTOR_AUTHORIZATION")!,
AllowedTools = new()
{
ToolNames =
{
"search_events",
"read_event"
}
},
ToolCallApprovalPolicy = RealtimeDefaultMcpToolCallApprovalPolicy.NeverRequireApproval
}
}
}), timeout.Token);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17access_token = ENV.fetch("OPENAI_MCP_ACCESS_TOKEN")
connection.session.update(
type: :realtime,
model: "gpt-realtime-1.5",
output_modalities: [:text],
tools: [
{
type: :mcp,
server_label: "google_calendar",
connector_id: "connector_googlecalendar",
authorization: access_token,
allowed_tools: ["search_events", "read_event"],
require_approval: :never
}
]
)
Los servidores MCP remotos
no reciben automáticamente todo el contexto de la conversación, pero pueden ver cualquier dato que el modelo envíe en una llamada a una herramienta.
Limita las herramientas disponibles con allowed_tools y exige aprobación para cualquier acción que no ejecutarías automáticamente.
A diferencia de las herramientas de tipo function de Realtime, las herramientas MCP remotas las ejecuta la propia Realtime API. Tu cliente no ejecuta la herramienta remota ni devuelve un elemento function_call_output. En cambio, configura el acceso, escucha los eventos del ciclo de vida de MCP y, si el servidor solicita aprobación, puede enviar una respuesta a esa solicitud.
El flujo típico es el siguiente:
- Envías
session.update o response.create con una entrada en tools cuyo type es mcp.
- El servidor empieza a importar las herramientas y emite
mcp_list_tools.in_progress.
- Mientras se genera la lista, el modelo no puede llamar a una herramienta que aún no haya terminado de cargarse. Si quieres esperar antes de iniciar un turno que dependa de esas herramientas, escucha el evento
mcp_list_tools.completed. El evento conversation.item.done cuyo item.type es mcp_list_tools muestra los nombres de las herramientas que se importaron. Si la importación falla, recibirás mcp_list_tools.failed.
- El usuario habla o envía texto. Tu cliente crea una respuesta o esta se crea automáticamente según la configuración de la sesión.
- Si el modelo elige una herramienta MCP, verás
response.mcp_call_arguments.delta y response.mcp_call_arguments.done.
- Si se requiere aprobación, el servidor agrega a la conversación un elemento cuyo
item.type es mcp_approval_request. Tu cliente debe responder con un elemento mcp_approval_response.
- Cuando la herramienta empiece a ejecutarse, verás
response.mcp_call.in_progress. Si la ejecución se completa correctamente, luego recibirás un evento response.output_item.done cuyo item.type es mcp_call. Si falla, recibirás response.mcp_call.failed.
- El evento
response.done de una respuesta puede llegar antes de que terminen sus llamadas MCP. Cuando hayan terminado tanto la respuesta como todas sus llamadas MCP, envía otro evento response.create para que el modelo use los resultados y continúe la conversación. Repite este paso si el modelo hace más llamadas MCP. La Realtime API no crea automáticamente estas respuestas de seguimiento.
Este manejador de eventos registra los principales eventos del ciclo de vida de MCP. No gestiona las respuestas de seguimiento.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82function parseRealtimeEvent(rawMessage) {
if (typeof rawMessage === "string") {
return JSON.parse(rawMessage);
}
if (typeof rawMessage?.data === "string") {
return JSON.parse(rawMessage.data);
}
return JSON.parse(rawMessage.toString());
}
function getOutputText(item) {
if (item.type !== "message") return "";
return (item.content ?? [])
.filter((part) => part.type === "output_text")
.map((part) => part.text)
.join("");
}
ws.on("message", (rawMessage) => {
const event = parseRealtimeEvent(rawMessage);
switch (event.type) {
case "mcp_list_tools.in_progress":
console.log("Listing MCP tools for item:", event.item_id);
break;
case "mcp_list_tools.completed":
console.log("MCP tool listing complete for item:", event.item_id);
break;
case "mcp_list_tools.failed":
console.error("MCP tool listing failed for item:", event.item_id);
break;
case "conversation.item.done":
if (event.item.type === "mcp_list_tools") {
const names = event.item.tools.map((tool) => tool.name).join(", ");
console.log(`MCP tools ready on ${event.item.server_label}: ${names}`);
}
if (event.item.type === "mcp_approval_request") {
console.log(
"Approval required for:",
event.item.name,
event.item.arguments
);
}
break;
case "response.mcp_call_arguments.done":
console.log("Final MCP call arguments:", event.arguments);
break;
case "response.mcp_call.in_progress":
console.log("Running MCP tool for item:", event.item_id);
break;
case "response.mcp_call.failed":
console.error("MCP tool call failed for item:", event.item_id);
break;
case "response.output_item.done":
if (event.item.type === "mcp_call") {
console.log(
`MCP output from ${event.item.server_label}.${event.item.name}:`,
event.item.output
);
}
if (event.item.type === "message") {
console.log("Assistant:", getOutputText(event.item));
}
break;
case "response.done":
console.log("Realtime turn complete.");
break;
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61def on_message(ws, message):
event = json.loads(message)
event_type = event["type"]
if event_type == "mcp_list_tools.in_progress":
print("Listing MCP tools for item:", event["item_id"])
return
if event_type == "mcp_list_tools.completed":
print("MCP tool listing complete for item:", event["item_id"])
return
if event_type == "mcp_list_tools.failed":
print("MCP tool listing failed for item:", event["item_id"])
return
if event_type == "conversation.item.done":
item = event["item"]
if item["type"] == "mcp_list_tools":
names = ", ".join(tool["name"] for tool in item["tools"])
print(f"MCP tools ready on {item['server_label']}: {names}")
return
if item["type"] == "mcp_approval_request":
print("Approval required for:", item["name"], item["arguments"])
return
if event_type == "response.mcp_call_arguments.done":
print("Final MCP call arguments:", event["arguments"])
return
if event_type == "response.mcp_call.in_progress":
print("Running MCP tool for item:", event["item_id"])
return
if event_type == "response.mcp_call.failed":
print("MCP tool call failed for item:", event["item_id"])
return
if event_type == "response.output_item.done":
item = event["item"]
if item["type"] == "mcp_call":
print(
f"MCP output from {item['server_label']}.{item['name']}:",
item.get("output"),
)
return
if item["type"] == "message":
text_parts = [
part["text"]
for part in item.get("content", [])
if part["type"] == "output_text"
]
print("Assistant:", "".join(text_parts))
return
if event_type == "response.done":
print("Realtime turn complete.")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// Call for each received event. Tool results can arrive after response.done.
func logMCPEvent(event realtime.RealtimeServerEventUnion) error {
switch e := event.AsAny().(type) {
case realtime.RealtimeErrorEvent:
return fmt.Errorf("Realtime API error: %s", e.Error.Message)
case realtime.McpListToolsFailed, realtime.ResponseMcpCallFailed:
return fmt.Errorf("MCP failed: %s", event.Type)
case realtime.ResponseOutputItemDoneEvent:
item := e.Item.OfRealtimeMcpToolCall
if item.Type == "mcp_call" {
if item.JSON.Error.Valid() || !item.JSON.Output.Valid() {
return fmt.Errorf("MCP call failed or has no output")
}
fmt.Fprintln(log.Writer(), "Tool output:", item.Output)
}
}
return nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77completed_events = {}
expected_calls = []
completed_calls = {}
complete = connection.find do |event|
case event
when OpenAI::Realtime::McpListToolsInProgress
puts("Listing MCP tools for item: #{event.item_id}")
when OpenAI::Realtime::McpListToolsFailed
raise "MCP tool listing failed for item: #{event.item_id}"
when OpenAI::Realtime::McpListToolsCompleted
puts("MCP tools ready for item: #{event.item_id}")
connection.response.create(
output_modalities: [:text],
input: [
{
type: :message,
role: :user,
content: [
{
type: :input_text,
text: "Which Realtime API transport should browser clients use?"
}
]
}
],
tool_choice: :required,
parallel_tool_calls: false
)
when OpenAI::Realtime::ConversationItemDone
item = event.item
case item
when OpenAI::Realtime::RealtimeMcpListTools
names = item.tools.map(&:name).join(", ")
puts("MCP tools ready on #{item.server_label}: #{names}")
when OpenAI::Realtime::RealtimeMcpApprovalRequest
puts("Approval required for: #{item.name} #{item.arguments}")
end
when OpenAI::Realtime::ResponseMcpCallArgumentsDone
puts("Final MCP call arguments: #{event.arguments}")
when OpenAI::Realtime::ResponseMcpCallInProgress
puts("Running MCP tool for item: #{event.item_id}")
when OpenAI::Realtime::ResponseMcpCallCompleted
puts("MCP tool call completed: #{event.item_id}")
when OpenAI::Realtime::ResponseMcpCallFailed
raise "MCP tool call failed: #{event.item_id}"
when OpenAI::Realtime::ResponseOutputItemDoneEvent
item = event.item
case item
when OpenAI::Realtime::RealtimeMcpToolCall
raise "MCP tool call failed" if item.error || item.output.nil?
completed_calls[item.id] = true
puts("MCP output from #{item.server_label}.#{item.name}: #{item.output}")
when OpenAI::Realtime::RealtimeConversationItemAssistantMessage
text = item.content.filter_map do |content|
content.text if content.type == :output_text
end.join
puts("Assistant: #{text}")
end
when OpenAI::Realtime::RealtimeErrorEvent
raise "Realtime API error: #{event.error.message}"
when OpenAI::Realtime::ResponseDoneEvent
raise "Realtime response did not complete" unless event.response.status == :completed
expected_calls.replace(Array(event.response.output).grep(OpenAI::Realtime::RealtimeMcpToolCall).map(&:id))
raise "The response did not call an MCP tool" if expected_calls.empty?
completed_events[:response] = true
end
if completed_events[:response] && expected_calls.all? { |id| completed_calls[id] }
puts("Realtime turn complete.")
true
else
false
end
end
raise "Connection closed before the MCP tool result arrived" unless complete
mcp_list_tools.failed: la Realtime API no pudo importar las herramientas del servidor remoto o del conector. Revisa server_url o connector_id, la autenticación, la conectividad del servidor y los nombres que hayas especificado en allowed_tools.
response.mcp_call.failed: el modelo eligió una herramienta, pero la llamada no se completó. Revisa los datos del evento y el elemento mcp_call que llega después para identificar errores del protocolo MCP, de ejecución o de transporte.
- Hay un elemento
mcp_approval_request sin un mcp_approval_response correspondiente: la llamada a la herramienta no puede continuar hasta que tu cliente la apruebe o la rechace explícitamente.
- Un turno comienza mientras
mcp_list_tools.in_progress sigue activo: en ese turno solo se pueden usar las herramientas que ya terminaron de cargarse.
- Una respuesta usa
tool_choice: "required", pero en ese momento no hay herramientas disponibles: el modelo no tiene ninguna herramienta a la que pueda llamar. Espera a recibir mcp_list_tools.completed, confirma que se haya importado al menos una herramienta o usa otro valor de tool_choice para los turnos que no requieran herramientas.
- La validación de la definición de una herramienta MCP falla antes de que empiece la importación. Entre las causas comunes están duplicar
server_label en el mismo arreglo tools, especificar tanto server_url como connector_id, omitir ambos campos en la solicitud inicial de creación de la sesión, usar un connector_id no válido o enviar tanto authorization como headers.Authorization. Si usas conectores, nunca envíes headers.Authorization.
Si una herramienta requiere aprobación, la Realtime API inserta un elemento mcp_approval_request en la conversación. Para continuar, envía un nuevo evento conversation.item.create cuyo item.type sea mcp_approval_response.
1
2
3
4
5
6
7
8
9
10
11
12function approveMcpRequest(approvalRequestId) {
const event = {
type: "conversation.item.create",
item: {
type: "mcp_approval_response",
approval_request_id: approvalRequestId,
approve: true,
},
};
ws.send(JSON.stringify(event));
}
1
2
3
4
5
6
7
8
9
10
11
12# Use the ID from the received MCP approval-request item.
def approve_mcp_request(ws, approval_request_id):
event = {
"type": "conversation.item.create",
"item": {
"type": "mcp_approval_response",
"approval_request_id": approval_request_id,
"approve": True,
},
}
ws.send(json.dumps(event))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// Call with the approval request received in conversation.item.done.
func approveDocumentationSearch(
ctx context.Context,
conn *realtime.RealtimeConnection,
request realtime.RealtimeMcpApprovalRequest,
) error {
// This example permits only a read-only search of the OpenAI documentation.
if request.ID == "" || request.ServerLabel != "openai_docs" || request.Name != "search_openai_docs" {
return fmt.Errorf("unapproved tool requested")
}
// Derive a distinct item ID within the API's 32-character limit.
approvalID := sha256.Sum256([]byte(request.ID))
return conn.Send(ctx, realtime.RealtimeClientEventParamOfConversationItemCreate(realtime.RealtimeMcpApprovalResponseParam{
ID: fmt.Sprintf("%x", approvalID[:16]),
ApprovalRequestID: request.ID,
Approve: true,
}))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.core.JsonMissing;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
import com.openai.models.responses.ToolChoiceOptions;
// Replace https://mcp.example.com/mcp with your company's MCP server URL.
static void approveMcpRequest(RealtimeConnection connection, String approvalRequestId)
throws Exception {
send(
connection,
RealtimeClientEvent.ofConversationItemCreate(
ConversationItemCreateEvent.builder()
.item(
RealtimeMcpApprovalResponse.builder()
.id(java.util.UUID.randomUUID().toString().replace("-", ""))
.approvalRequestId(approvalRequestId)
.approve(true)
.build())
.build()));
}
// Retry only explicit admission rejections; these guarantee nothing was sent.
private static void send(RealtimeConnection connection, RealtimeClientEvent event)
throws Exception {
long deadline = System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(5);
while (true) {
try {
connection.send(event);
return;
} catch (com.openai.core.http.WebSocketWriteNotAttempted.Busy busy) {
if (System.nanoTime() >= deadline) throw busy;
Thread.sleep(10);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13using System.Text.Json;
using OpenAI.Realtime;
#pragma warning disable OPENAI002
// Connect to the official OpenAI documentation MCP server.
internal static async Task ApproveMcpRequestAsync(RealtimeSessionClient session, string approvalRequestId, CancellationToken cancellationToken)
{
await session.SendCommandAsync(new RealtimeClientCommandConversationItemCreate(new RealtimeMcpToolCallApprovalResponseItem(approvalRequestId, true)
{
Id = Guid.NewGuid().ToString("N")
}), cancellationToken);
}
1
2
3
4
5
6
7
8
9# Use the ID from the received MCP approval-request item.
approval_request_id = item.id
connection.conversation.items.create(
type: :mcp_approval_response,
id: "approval_#{SecureRandom.hex(8)}",
approval_request_id: approval_request_id,
approve: true
)
Si rechazas la solicitud, establece approve en false y, si quieres, incluye el motivo en reason.
Si MCP debe estar disponible solo durante un turno, agrega el mismo objeto de herramienta MCP a response.tools en lugar de session.tools:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31const event = {
type: "response.create",
response: {
output_modalities: ["text"],
tool_choice: "required",
parallel_tool_calls: false,
input: [
{
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "Which transport should I use for browser clients in the Realtime API?",
},
],
},
],
tools: [
{
type: "mcp",
server_label: "openai_docs",
server_url: "https://developers.openai.com/mcp",
allowed_tools: ["search_openai_docs", "fetch_openai_doc"],
require_approval: "never",
},
],
},
};
ws.send(JSON.stringify(event));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31event = {
"type": "response.create",
"response": {
"output_modalities": ["text"],
"tool_choice": "required",
"parallel_tool_calls": False,
"input": [
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "Which transport should I use for browser clients in the Realtime API?",
}
],
}
],
"tools": [
{
"type": "mcp",
"server_label": "openai_docs",
"server_url": "https://developers.openai.com/mcp",
"allowed_tools": ["search_openai_docs", "fetch_openai_doc"],
"require_approval": "never",
}
],
},
}
ws.send(json.dumps(event))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// input contains the conversation items for this response.
if err := conn.Send(ctx, realtime.RealtimeClientEventUnionParam{
OfResponseCreate: &realtime.ResponseCreateEventParam{
Response: realtime.RealtimeResponseCreateParams{
ParallelToolCalls: openai.Bool(false),
Input: input,
OutputModalities: []string{"text"},
ToolChoice: realtime.RealtimeResponseCreateParamsToolChoiceUnion{
OfToolChoiceMode: param.NewOpt[responses.ToolChoiceOptions]("required"),
},
Tools: []realtime.RealtimeResponseCreateParamsToolUnion{
{
OfMcp: &realtime.RealtimeResponseCreateMcpToolParam{
ServerLabel: "openai_docs",
ServerURL: openai.String("https://developers.openai.com/mcp"),
AllowedTools: realtime.RealtimeResponseCreateMcpToolAllowedToolsUnionParam{
OfMcpAllowedTools: []string{"search_openai_docs", "fetch_openai_doc"},
},
RequireApproval: realtime.RealtimeResponseCreateMcpToolRequireApprovalUnionParam{
OfMcpToolApprovalSetting: openai.String("never"),
},
},
},
},
},
},
}); err != nil {
return err
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.core.JsonMissing;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
import java.util.List;
connection.send(
RealtimeClientEvent.ofResponseCreate(
ResponseCreateEvent.builder()
.response(
RealtimeResponseCreateParams.builder()
.toolChoice(com.openai.models.responses.ToolChoiceOptions.REQUIRED)
.parallelToolCalls(false)
.metadata(
RealtimeResponseCreateParams.Metadata.builder()
.putAdditionalProperty(
"topic", com.openai.core.JsonValue.from("mcp_initial"))
.build())
.addOutputModality(RealtimeResponseCreateParams.OutputModality.TEXT)
.addInput(
RealtimeConversationItemUserMessage.builder()
.object_(JsonMissing.of())
.addContent(
RealtimeConversationItemUserMessage.Content.builder()
.type(
RealtimeConversationItemUserMessage.Content.Type
.INPUT_TEXT)
.text(
"Which transport should I use for browser clients in the Realtime API?")
.build())
.build())
.addTool(
RealtimeResponseCreateMcpTool.builder()
.serverLabel("openai_docs")
.serverUrl("https://developers.openai.com/mcp")
.allowedToolsOfMcp(
List.of("search_openai_docs", "fetch_openai_doc"))
.requireApproval(
RealtimeResponseCreateMcpTool.RequireApproval
.McpToolApprovalSetting.NEVER)
.build())
.build())
.build()));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36using System.Text.Json;
using OpenAI.Realtime;
#pragma warning disable OPENAI002
await session.SendCommandAsync(new RealtimeClientCommandResponseCreate
{
ResponseOptions = new()
{
Metadata = new Dictionary<string, BinaryData> { ["topic"] = BinaryData.FromObjectAsJson("mcp_initial") },
ToolChoice = RealtimeDefaultToolChoice.Required,
OutputModalities =
{
RealtimeOutputModality.Text
},
InputItems =
{
RealtimeItem.CreateUserMessageItem("Which transport should I use for browser clients in the Realtime API?")
},
Tools =
{
new RealtimeMcpTool("openai_docs", new Uri("https://developers.openai.com/mcp"))
{
AllowedTools = new()
{
ToolNames =
{
"search_openai_docs",
"fetch_openai_doc"
}
},
ToolCallApprovalPolicy = RealtimeDefaultMcpToolCallApprovalPolicy.NeverRequireApproval
}
}
}
}, timeout.Token);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26connection.response.create(
output_modalities: [:text],
tool_choice: :required,
parallel_tool_calls: false,
input: [
{
type: :message,
role: :user,
content: [
{
type: :input_text,
text: "Which Realtime API transport should browser clients use?"
}
]
}
],
tools: [
{
type: :mcp,
server_label: "openai_docs",
server_url: "https://developers.openai.com/mcp",
allowed_tools: ["search_openai_docs", "fetch_openai_doc"],
require_approval: :never
}
]
)
Esto es útil cuando solo una respuesta necesita contexto externo o cuando distintos turnos deben usar distintos servidores MCP.
server_label es el identificador estable de una definición de herramienta en la sesión de Realtime actual. Una vez que definas un servidor o conector con server_label y con uno de los campos server_url o connector_id, en los eventos session.update o response.create posteriores basta con indicar ese mismo server_label. La Realtime API reutilizará la definición anterior sin que tengas que volver a enviar el objeto completo de la herramienta.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27const event = {
type: "response.create",
response: {
output_modalities: ["text"],
input: [
{
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "Check my schedule for this afternoon.",
},
],
},
],
// Reuses the google_calendar connector defined earlier in this session.
tools: [
{
type: "mcp",
server_label: "google_calendar",
},
],
},
};
ws.send(JSON.stringify(event));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27event = {
"type": "response.create",
"response": {
"output_modalities": ["text"],
"input": [
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "Check my schedule for this afternoon.",
}
],
}
],
# Reuses the google_calendar connector defined earlier in this session.
"tools": [
{
"type": "mcp",
"server_label": "google_calendar",
}
],
},
}
ws.send(json.dumps(event))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// input contains the conversation items for this response.
if err := conn.Send(ctx, realtime.RealtimeClientEventUnionParam{
OfResponseCreate: &realtime.ResponseCreateEventParam{
Response: realtime.RealtimeResponseCreateParams{
OutputModalities: []string{"text"},
Input: input,
Tools: []realtime.RealtimeResponseCreateParamsToolUnion{
{
OfMcp: &realtime.RealtimeResponseCreateMcpToolParam{
ServerLabel: "google_calendar",
},
},
},
ToolChoice: realtime.RealtimeResponseCreateParamsToolChoiceUnion{
OfToolChoiceMode: param.NewOpt(responses.ToolChoiceOptions("required")),
},
},
},
}); err != nil {
return err
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.helpers.RealtimeConnection;
import com.openai.helpers.RealtimeWebSocketOptions;
import com.openai.models.realtime.*;
import java.util.List;
send(
connection,
RealtimeClientEvent.ofResponseCreate(
ResponseCreateEvent.builder()
.response(
RealtimeResponseCreateParams.builder()
.metadata(
RealtimeResponseCreateParams.Metadata.builder()
.putAdditionalProperty(
"topic", com.openai.core.JsonValue.from("mcp_initial"))
.build())
.addOutputModality(RealtimeResponseCreateParams.OutputModality.TEXT)
.addInput(
RealtimeConversationItemUserMessage.builder()
.addContent(
RealtimeConversationItemUserMessage.Content.builder()
.type(
RealtimeConversationItemUserMessage.Content.Type
.INPUT_TEXT)
.text("Check my schedule for this afternoon.")
.build())
.build())
.addTool(
RealtimeResponseCreateMcpTool.builder()
.serverLabel("google_calendar")
.build())
.build())
.build()));
// Retry only explicit admission rejections; these guarantee nothing was sent.
private static void send(RealtimeConnection connection, RealtimeClientEvent event)
throws Exception {
long deadline = System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(5);
while (true) {
try {
connection.send(event);
return;
} catch (com.openai.core.http.WebSocketWriteNotAttempted.Busy busy) {
if (System.nanoTime() >= deadline) throw busy;
Thread.sleep(10);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26using OpenAI.Realtime;
#pragma warning disable OPENAI002
await session.SendCommandAsync(new RealtimeClientCommandResponseCreate
{
ResponseOptions = new()
{
Metadata = new Dictionary<string, BinaryData> { ["topic"] = BinaryData.FromObjectAsJson("mcp_initial") },
OutputModalities =
{
RealtimeOutputModality.Text
},
InputItems =
{
RealtimeItem.CreateUserMessageItem("Check my schedule for this afternoon.")
},
Tools =
{
new RealtimeMcpTool
{
ServerLabel = "google_calendar"
}
}
}
}, timeout.Token);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21connection.response.create(
output_modalities: [:text],
input: [
{
type: :message,
role: :user,
content: [
{
type: :input_text,
text: "Check my schedule this afternoon."
}
]
}
],
tools: [
{
type: :mcp,
server_label: "google_calendar"
}
]
)
Solo puedes reutilizar la definición dentro de la misma sesión. Si inicias una nueva sesión de Realtime, vuelve a enviar la definición MCP completa para que el servidor pueda importar su lista de herramientas.