Ir a la documentación
DocumentaciónProxy HTTP

Proxy HTTP

Envía cualquier solicitud HTTP a un servicio conectado a través de Sallyport. La pasarela pone la clave, así que tu script nunca la tiene.

En esta página

La dirección

Envía la solicitud a https://mcp.sallyport.cloud/p/{binding_id}/{path} con un token de agente en Authorization: Bearer. Sallyport pone la clave de la conexión, llama al servicio y te devuelve su respuesta en streaming.

El servicio recibe https://{host}{path_prefix}, luego la ruta que escribiste después del ID y luego tu query string. Una conexión del catálogo tiene el prefijo /, así que escribe la ruta completa de la documentación de la API del proveedor.

Una conexión que cubre todos los hosts de un servicio, como AWS en *.amazonaws.com, recibe el host como primer segmento: /p/{binding_id}/sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15.

Qué necesitas

  • Un token de agente. Abre el agente en Agentes y pulsa Nuevo token en la sección Tokens. El valor se muestra una sola vez.
  • El ID de una conexión HTTP. Abre Conexiones y haz clic en la conexión: el ID es la última parte de la dirección de la página.
  • Un agente y una conexión del mismo espacio de trabajo y entorno. Sallyport da cada clave nueva a todos los agentes de su espacio, así que esto ya se cumple salvo que alguien lo haya quitado.

Lee tu cuenta de GitHub

Shell · GET a través de una conexión de GitHub
export SP_AGENT_TOKEN='sp_live_…'
export SP_BINDING_ID='…'

curl --fail-with-body \
  --header "Authorization: Bearer $SP_AGENT_TOKEN" \
  "https://mcp.sallyport.cloud/p/$SP_BINDING_ID/user"

Vuelven el estado, las cabeceras y el JSON del propio GitHub, con X-Sallyport-Call-Id, que nombra la llamada. La respuesta llega por partes sin Content-Length, y llegan todas las cabeceras, también Set-Cookie.

Envía una escritura

Shell · POST con una intención
curl --fail-with-body \
  --request POST \
  --header "Authorization: Bearer $SP_AGENT_TOKEN" \
  --header "Content-Type: application/json" \
  --header "X-Sallyport-Intent: File the bug the user reported" \
  --data '{"title":"Checkout fails on Safari"}' \
  "https://mcp.sallyport.cloud/p/$SP_BINDING_ID/repos/acme/web/issues"
CabeceraQué hace
X-Sallyport-IntentUna frase de hasta 500 caracteres sobre por qué se hace la llamada. Una persona la lee en la tarjeta de aprobación. Una llamada que espera a una persona sin ella recibe 400 con reason: "missing_intent".
X-Sallyport-Idempotency-KeyTu propia clave para una escritura. Una repetición con la misma clave y la misma solicitud recibe la primera respuesta durante 24 horas en lugar de ejecutarse otra vez.
X-Sallyport-Call-IdContinúa una llamada cuyo ID ya tienes, diga lo que diga el resto de la solicitud. No se crea una segunda llamada.

Sallyport nunca reenvía estas tres cabeceras. También quita Authorization, Cookie, Host, Content-Length, Expect, Forwarded, Via y toda cabecera X-Forwarded-*.

Una cabecera en el lugar de la clave, como PRIVATE-TOKEN de GitLab, se quita. Su nombre queda en el registro, y la llamada en Actividad muestra su valor, marcado como no enviado. Los cambios de método como _method y X-HTTP-Method-Override se eliminan, así que se ejecuta el método que envías.

Repeticiones

Una solicitud GET, HEAD u OPTIONS siempre se ejecuta. Una escritura sin tu propia clave recibe una calculada a partir de la solicitud, así que dos escrituras idénticas son una sola llamada.

Si la repites mientras la primera espera a una persona, se une a la misma tarjeta. Mientras la primera se ejecuta, recibe esa respuesta o 409 IN_FLIGHT. Durante 2 minutos tras el final, recibe la primera respuesta.

Una respuesta devuelta lleva X-Sallyport-Deduplicated: true y el X-Sallyport-Call-Id de la primera llamada. Si el servicio rechazó la escritura con un 4xx, la clave se libera y la repetición corregida se ejecuta. La misma clave con otra solicitud recibe 409 IDEMPOTENCY_CONFLICT.

Cuando una persona tiene que aprobar

Una llamada espera cuando la regla del agente para esa conexión dice preguntar, por ejemplo tras activar Preguntar en cada llamada. La respuesta llega enseguida, con 202, Retry-After y este cuerpo:

JSON · 202 mientras la llamada espera
{
  "code": "APPROVAL_PENDING",
  "instruction": "Show the user approve_url. After poll_after_ms repeat this same call with call_id — you get the result or \"still waiting\" again. This continues the same wait; it is not an idempotent cache. Do not create a new call.",
  "call_id": "sp_c_4pQx…",
  "approve_url": "https://my.sallyport.cloud/approve/sp_c_4pQx…",
  "class": "write",
  "summary": "POST api.github.com/repos/acme/web/issues",
  "expires_at": "2026-09-29T19:05:00Z",
  "poll_after_ms": 5000,
  "expected_wait_ms": 42000,
  "poll_url": "https://mcp.sallyport.cloud/p/calls/sp_c_4pQx…"
}

Muestra approve_url a la persona. Luego consulta poll_url con el mismo token tras Retry-After segundos. Sirve cualquier método y nada se ejecuta dos veces.

Respuesta a la consultaSignificado
202 con APPROVAL_PENDINGNadie ha respondido todavía. Vuelve a preguntar tras Retry-After.
La respuesta del propio servicioLa persona aprobó. La llamada se ejecuta ahora y su respuesta llega a esta consulta.
202 con stateLa llamada sigue en el servicio. Vuelve a preguntar en un segundo.
403 APPROVAL_DENIEDLa persona rechazó. reason y decided_by dicen por qué y quién. No se envió nada.
410 APPROVAL_EXPIREDNadie respondió a tiempo. Envía la solicitud de nuevo sin X-Sallyport-Call-Id para una tarjeta nueva.
404 CALL_NOT_FOUNDEl call_id pertenece a otro agente o no existe. Haz una llamada nueva.
200 con state y bytesLa llamada terminó y su respuesta ya salió. status es el estado del servicio, y body_ref aparece cuando la respuesta se guardó.
error_code con state y bytesLa llamada terminó con un fallo, como UPSTREAM_TOO_LARGE. La línea de estado es la de ese código, e instruction dice qué hacer.

Una respuesta de más de 1 MiB también se guarda, 30 días por defecto. Una consulta de la llamada terminada trae su body_ref, y la llamada en Actividad ofrece Descargar la respuesta completa. Descárgala con GET https://mcp.sallyport.cloud/v1/bodies/{body_ref} y el mismo token de agente; Range funciona.

WebSocket

Abre wss://mcp.sallyport.cloud/p/{binding_id}/{path} con el mismo token de agente en Authorization: Bearer. Sallyport pone la clave de la conexión donde el servicio la lee, en el handshake para la mayoría de los servicios, abre wss://{host}{path} en el servicio y pasa cada frame en ambos sentidos tal como se envió: texto, binario, ping, pong y el código de cierre. El subprotocolo que pides en Sec-WebSocket-Protocol llega al servicio, y su elección vuelve.

JavaScript · transcripción en tiempo real de ElevenLabs, con el paquete ws
import WebSocket from 'ws'

const socket = new WebSocket(
  `wss://mcp.sallyport.cloud/p/${process.env.SP_BINDING_ID}/v1/speech-to-text/realtime` +
    '?model_id=scribe_v2_realtime&audio_format=pcm_16000&commit_strategy=vad',
  { headers: { Authorization: `Bearer ${process.env.SP_AGENT_TOKEN}` } },
)

// 16 kHz 16-bit mono PCM, every 20–100 ms
function sendAudio(pcm) {
  socket.send(JSON.stringify({ message_type: 'input_audio_chunk', audio_base_64: pcm.toString('base64') }))
}

socket.on('message', (data) => {
  const event = JSON.parse(data.toString())
  if (event.message_type === 'committed_transcript') console.log(event.text)
})

// 202: the session waits for a person. 4xx: the refusal, as JSON.
socket.on('unexpected-response', (_request, response) => console.error(response.statusCode))

La sesión es una llamada, y la decide la regla del agente para la conexión, como cualquier otra. La transcripción en tiempo real de ElevenLabs es una escritura de pago y pasa directa salvo que Preguntar en cada llamada esté activado. La llamada en Actividad muestra cuándo se abrió y se cerró la sesión, quién la cerró y con qué código, qué envió cada lado y cada mensaje en ambos sentidos tal como se envió. Los mensajes se guardan 30 días, como una respuesta larga.

LímiteValorAl pasarlo
Duración de la sesión60 minutosAmbos lados reciben el cierre 1001 con el motivo.
Un mensaje, en cualquier sentido1 MiBAmbos lados reciben el cierre 1009. El mensaje no se pasa.
Silencio en ambos sentidos10 minutos sin un frameAmbos lados reciben el cierre 1001 con el motivo.

Cuando la sesión tiene que esperar a una persona, el handshake recibe 202 y el cuerpo APPROVAL_PENDING de arriba en lugar de 101. Cuando la persona aprueba, abre de nuevo el WebSocket con X-Sallyport-Call-Id: {call_id} y empieza la sesión aprobada. Una consulta a poll_url responde entonces 426 WEBSOCKET_REQUIRED, porque una sesión solo corre sobre un WebSocket. Un servicio que rechaza el handshake, por una clave o un parámetro incorrectos, te envía su propio estado y cuerpo.

Servicios que funcionan por WebSocket

Cada uno abre su API WebSocket a través de /p/, y Sallyport pone la clave donde el proveedor la lee. Escribes la dirección y los mensajes tal como los documenta el proveedor. list_access con service dice lo mismo para cada conexión.

ServiciosDónde pone Sallyport la clave
OpenAI Realtime, Azure OpenAI Realtime, Gemini Live, xAI, Mistral, Deepgram, AssemblyAI, Speechmatics, Cartesia, MiniMax, Together AI, fal, Murf, Telnyx, ElevenLabs, Retell, GladiaEn el handshake, como en cada llamada
Rev.ai, HumeEn la query: access_token, api_key
Datos de mercado de Alpaca, CoinGecko, CoinMarketCap, AlchemyEn el handshake
Finnhub, Twelve DataEn la query: token, apikey
Massive, Financial Modeling Prep, actualizaciones de órdenes de AlpacaEn el primer mensaje que envías
Binance, Bybit, OKX, Kraken, Coinbase, Coinbase ExchangeEn ninguna parte: los flujos públicos no la necesitan, y el flujo privado de Kraken toma el token que responde su llamada GetWebSocketsToken
Gateway de DiscordEn el primer mensaje que envías: el d.token de IDENTIFY
Slack Socket Mode, Twitch EventSub, RingCentral, Vapi, consola de HetznerEn ninguna parte: abre la URL con la que respondió el servicio, con su ticket
Railway, Render, Koyeb, Daytona, Mattermost, ntfy, Grafana CloudEn el handshake
Browserless, Steel, BrowserbaseEn la query: token, apiKey

Algunos servicios tienen su WebSocket en un host propio: gateway.discord.gg, agent.deepgram.com, ws.finnhub.io, el wss-….slack.com de un ticket de Socket Mode. Ese host se abre por la conexión de ese servicio, como primer segmento de la ruta: wss://mcp.sallyport.cloud/p/{binding_id}/gateway.discord.gg/?v=10&encoding=json. Solo un WebSocket llega a él.

Cuando la clave va en el primer mensaje, envía el mensaje que documenta el proveedor con "{{credential}}" en lugar de la clave, o sin ese campo. Sallyport escribe la clave antes de que el mensaje llegue al servicio, y la llamada en Actividad muestra tu mensaje tal como lo enviaste.

JavaScript · OpenAI Realtime, una respuesta de texto
import WebSocket from 'ws'

const socket = new WebSocket(
  `wss://mcp.sallyport.cloud/p/${process.env.SP_BINDING_ID}/v1/realtime?model=gpt-realtime`,
  { headers: { Authorization: `Bearer ${process.env.SP_AGENT_TOKEN}` } },
)

socket.on('open', () => {
  socket.send(JSON.stringify({ type: 'session.update', session: { type: 'realtime', output_modalities: ['text'] } }))
  socket.send(JSON.stringify({
    type: 'conversation.item.create',
    item: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'Say hello in one sentence.' }] },
  }))
  socket.send(JSON.stringify({ type: 'response.create' }))
})

socket.on('message', (data) => {
  const event = JSON.parse(data.toString())
  if (event.type === 'response.output_text.delta') process.stdout.write(event.delta)
  if (event.type === 'response.done') socket.close()
})
JavaScript · transcripción en vivo con Deepgram
import WebSocket from 'ws'

const socket = new WebSocket(
  `wss://mcp.sallyport.cloud/p/${process.env.SP_BINDING_ID}/v1/listen` +
    '?model=nova-3&encoding=linear16&sample_rate=16000&interim_results=true',
  { headers: { Authorization: `Bearer ${process.env.SP_AGENT_TOKEN}` } },
)

// 16 kHz 16-bit mono PCM, as it arrives
function sendAudio(pcm) {
  socket.send(pcm)
}

socket.on('message', (data) => {
  const result = JSON.parse(data.toString())
  if (result.type === 'Results' && result.is_final) console.log(result.channel.alternatives[0].transcript)
})

// When the audio ends, Deepgram sends the last results and closes.
function finish() {
  socket.send(JSON.stringify({ type: 'CloseStream' }))
}

Rechazos

Un rechazo es JSON: {"error": {"code": …, "instruction": …}}, con los campos que su código necesita al lado. instruction es una frase en inglés sobre qué hacer después.

Cuatro códigos son resultados, no errores, y llegan como un objeto simple sin el envoltorio error: APPROVAL_PENDING, ACCESS_REQUEST_CREATED, ALREADY_EXECUTED y UPSTREAM_ERROR. En /p/ un cuerpo UPSTREAM_ERROR es la respuesta 424 cuando el servicio nunca respondió. Un servicio que responde con un error te envía su propia respuesta.

Estado y códigoQué pasó
401 AUTH_FAILEDEl token falta, es incorrecto, está revocado o solo sirve para /mcp. WWW-Authenticate indica a un cliente OAuth dónde conseguir el correcto.
403 SOURCE_IP_NOT_ALLOWEDLa solicitud vino de una dirección fuera de la lista permitida del agente.
423 SUSPENDEDEl agente, su espacio de trabajo o la organización están en pausa.
404 ACCESS_REQUEST_CREATEDEl agente no puede usar esta conexión: sin acceso, conexión en pausa o ID desconocido. Muestra approve_url a una persona y repite la llamada.
403 POLICY_DENYLa regla del agente para esta conexión rechaza esta clase de llamada.
404 CALL_NOT_FOUNDEl X-Sallyport-Call-Id o la llamada consultada pertenece a otro agente o no existe. Haz una llamada nueva.
400 BAD_REQUESTreason dice qué corregir: missing_intent, intent_too_long, body_too_large con max_bytes, missing_host, query_in_path, mcp_binding, database_binding, email_binding, email_operation, sftp_operation, websocket_handshake y otros.
400 PATH_OUT_OF_PREFIXLa ruta sale del límite de ruta de la conexión, por ejemplo con ...
Códigos 409IDEMPOTENCY_CONFLICT, IN_FLIGHT, CALL_CONFLICT o APPROVAL_SUPERSEDED. La instruction dice qué hacer.
426 UPGRADE_NOT_SUPPORTEDUn upgrade a cualquier protocolo que no sea WebSocket.
426 WEBSOCKET_REQUIREDUna consulta de una sesión WebSocket aprobada. Abre de nuevo el WebSocket con X-Sallyport-Call-Id.
429 RATE_LIMITEDUn límite de frecuencia del agente o de la conexión. Espera Retry-After y repite.
Códigos 424EGRESS_DENIED, SERVICE_NOT_CONNECTED, UPSTREAM_TOO_LARGE, OUTCOME_UNKNOWN o UPSTREAM_ERROR. Lee la instruction antes de repetir: OUTCOME_UNKNOWN significa que el servicio pudo actuar.
503 UNAVAILABLE, 500 INTERNALUn fallo del lado de Sallyport. Repite un 503 tras Retry-After.

Describe la solicitud en el cuerpo

Un cliente que no puede fijar el método o la ruta puede hacer POST con una descripción y Content-Type: application/vnd.sallyport.request+json. Todos los campos son opcionales y cada uno sustituye esa parte de la solicitud.

Shell · un DELETE descrito en un POST
curl --fail-with-body \
  --request POST \
  --header "Authorization: Bearer $SP_AGENT_TOKEN" \
  --header "Content-Type: application/vnd.sallyport.request+json" \
  --data '{"method":"DELETE","path":"/repos/acme/web/labels/stale","intent":"Remove the unused label"}' \
  "https://mcp.sallyport.cloud/p/$SP_BINDING_ID/"

Los campos son method, path, query, headers, body, intent e idempotency_key. path no admite ? ni #: los parámetros van en query. Un body JSON se envía como JSON, y una cadena como texto tal cual.

Una respuesta admite hasta 512 MiB. La página de la conexión muestra su propio límite en Límites, como Respuesta más grande del servicio. La respuesta se transmite a medida que llega, así que una que pasa el límite se corta. El estado y las cabeceras ya se enviaron, y el cuerpo queda incompleto. Una consulta de la llamada que nombra X-Sallyport-Call-Id muestra entonces UPSTREAM_TOO_LARGE.