Aller à la documentation
DocumentationProxy HTTP

Proxy HTTP

Envoyez n’importe quelle requête HTTP à un service connecté via Sallyport. La passerelle ajoute la clé, votre script ne la détient jamais.

Sur cette page

L’adresse

Envoyez la requête à https://mcp.sallyport.cloud/p/{binding_id}/{path} avec un jeton d’agent dans Authorization: Bearer. Sallyport ajoute la clé de la connexion, appelle le service et vous renvoie sa réponse en streaming.

Le service reçoit https://{host}{path_prefix}, puis le chemin écrit après l’ID, puis votre query string. Une connexion du catalogue a le préfixe / : écrivez donc le chemin complet de la documentation de l’API du fournisseur.

Une connexion qui couvre tous les hôtes d’un service, comme AWS sur *.amazonaws.com, prend l’hôte en premier segment : /p/{binding_id}/sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15.

Ce qu’il vous faut

  • Un jeton d’agent. Ouvrez l’agent dans Agents et cliquez sur Nouveau jeton dans la section Jetons. La valeur n’apparaît qu’une fois.
  • L’ID d’une connexion HTTP. Ouvrez Connexions et cliquez sur la connexion : l’ID est la dernière partie de l’adresse de la page.
  • Un agent et une connexion du même espace de travail et du même environnement. Sallyport donne chaque nouvelle clé à tous les agents de son espace, c’est donc déjà le cas sauf retrait.

Lire votre compte GitHub

Shell · GET via une connexion 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"

Le statut, les en-têtes et le JSON de GitHub lui-même reviennent, avec X-Sallyport-Call-Id qui nomme l’appel. La réponse arrive par morceaux sans Content-Length, et tous les en-têtes arrivent, Set-Cookie compris.

Envoyer une écriture

Shell · POST avec une intention
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"
En-têteRôle
X-Sallyport-IntentUne phrase de 500 caractères au plus sur la raison de l’appel. Une personne la lit sur la carte d’approbation. Un appel qui attend une personne sans elle reçoit 400 avec reason: "missing_intent".
X-Sallyport-Idempotency-KeyVotre propre clé pour une écriture. Une répétition avec la même clé et la même requête reçoit la première réponse pendant 24 heures au lieu de s’exécuter à nouveau.
X-Sallyport-Call-IdPoursuit un appel dont vous avez déjà l’ID, quel que soit le reste de la requête. Aucun second appel n’est créé.

Sallyport ne transmet jamais ces trois en-têtes. Il retire aussi Authorization, Cookie, Host, Content-Length, Expect, Forwarded, Via et tout en-tête X-Forwarded-*.

Un en-tête à la place de la clé, comme PRIVATE-TOKEN chez GitLab, est retiré. Son nom va dans le journal, et l’appel dans Activité montre sa valeur, marquée comme non envoyée. Les substitutions de méthode comme _method et X-HTTP-Method-Override sont retirées : c’est la méthode envoyée qui s’exécute.

Répétitions

Une requête GET, HEAD ou OPTIONS s’exécute toujours. Une écriture sans votre propre clé en reçoit une calculée à partir de la requête : deux écritures identiques font un seul appel.

Renvoyée pendant que la première attend une personne, elle rejoint la même carte. Pendant que la première s’exécute, elle reçoit sa réponse ou 409 IN_FLIGHT. Pendant 2 minutes après la fin, elle reçoit la première réponse.

Une réponse renvoyée porte X-Sallyport-Deduplicated: true et le X-Sallyport-Call-Id du premier appel. Si le service a refusé l’écriture avec un 4xx, la clé est libérée et la répétition corrigée s’exécute. La même clé avec une autre requête reçoit 409 IDEMPOTENCY_CONFLICT.

Quand une personne doit approuver

Un appel attend quand la règle de l’agent pour cette connexion dit de demander, par exemple après avoir activé Demander à chaque appel. La réponse arrive tout de suite, avec 202, Retry-After et ce corps :

JSON · 202 pendant que l’appel attend
{
  "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…"
}

Montrez approve_url à la personne. Interrogez ensuite poll_url avec le même jeton après Retry-After secondes. Toute méthode convient, et rien ne s’exécute deux fois.

Réponse au sondageSignification
202 avec APPROVAL_PENDINGPersonne n’a encore répondu. Redemandez après Retry-After.
La réponse du service lui-mêmeLa personne a approuvé. L’appel s’exécute maintenant et sa réponse arrive dans ce sondage.
202 avec stateL’appel est encore chez le service. Redemandez dans une seconde.
403 APPROVAL_DENIEDLa personne a refusé. reason et decided_by disent pourquoi et qui. Rien n’a été envoyé.
410 APPROVAL_EXPIREDPersonne n’a répondu à temps. Renvoyez la requête sans X-Sallyport-Call-Id pour une nouvelle carte.
404 CALL_NOT_FOUNDLe call_id appartient à un autre agent ou n’existe pas. Faites un nouvel appel.
200 avec state et bytesL’appel est terminé et sa réponse est déjà partie. status est le statut du service, et body_ref est présent quand la réponse a été stockée.
error_code avec state et bytesL’appel s’est terminé par un échec, comme UPSTREAM_TOO_LARGE. La ligne de statut est celle de ce code, et instruction dit quoi faire.

Une réponse de plus de 1 MiB est aussi stockée, 30 jours par défaut. Une interrogation de l’appel terminé porte son body_ref, et l’appel dans Activité propose Télécharger la réponse complète. Téléchargez-la avec GET https://mcp.sallyport.cloud/v1/bodies/{body_ref} et le même jeton d’agent ; Range fonctionne.

WebSocket

Ouvrez wss://mcp.sallyport.cloud/p/{binding_id}/{path} avec le même jeton d’agent dans Authorization: Bearer. Sallyport met la clé de la connexion là où le service la lit, dans la poignée de main pour la plupart des services, ouvre wss://{host}{path} chez le service et transmet chaque trame dans les deux sens telle qu’elle a été envoyée : texte, binaire, ping, pong et le code de fermeture. Le sous-protocole demandé dans Sec-WebSocket-Protocol arrive au service, et son choix vous revient.

JavaScript · transcription ElevenLabs en temps réel, avec le paquet 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 session est un seul appel, décidé comme tout autre par la règle de l’agent pour la connexion. La transcription ElevenLabs en temps réel est une écriture payante et passe directement, sauf si Demander à chaque appel est activé. L’appel dans Activité montre quand la session s’est ouverte et fermée, qui l’a fermée et avec quel code, ce que chaque côté a envoyé, et chaque message dans les deux sens tel qu’il a été envoyé. Les messages sont gardés 30 jours, comme une longue réponse.

LimiteValeurAu-delà
Durée de la session60 minutesLes deux côtés reçoivent la fermeture 1001 avec la raison.
Un message, dans un sens ou l’autre1 MiBLes deux côtés reçoivent la fermeture 1009. Le message n’est pas transmis.
Silence dans les deux sens10 minutes sans trameLes deux côtés reçoivent la fermeture 1001 avec la raison.

Quand la session doit attendre une personne, la poignée de main reçoit 202 et le corps APPROVAL_PENDING ci-dessus au lieu de 101. Une fois que la personne approuve, rouvrez le WebSocket avec X-Sallyport-Call-Id: {call_id} et la session approuvée commence. Une interrogation de poll_url répond alors 426 WEBSOCKET_REQUIRED, car une session ne passe que par un WebSocket. Un service qui refuse la poignée de main, pour une clé ou un paramètre erronés, vous envoie son propre statut et son corps.

Les services qui fonctionnent en WebSocket

Chacun ouvre son API WebSocket par /p/, et Sallyport met la clé là où le fournisseur la lit. Vous écrivez l’adresse et les messages tels que le fournisseur les documente. list_access avec service dit la même chose pour chaque connexion.

ServicesOù Sallyport met la clé
OpenAI Realtime, Azure OpenAI Realtime, Gemini Live, xAI, Mistral, Deepgram, AssemblyAI, Speechmatics, Cartesia, MiniMax, Together AI, fal, Murf, Telnyx, ElevenLabs, Retell, GladiaDans la poignée de main, comme à chaque appel
Rev.ai, HumeDans la query : access_token, api_key
Données de marché Alpaca, CoinGecko, CoinMarketCap, AlchemyDans la poignée de main
Finnhub, Twelve DataDans la query : token, apikey
Massive, Financial Modeling Prep, mises à jour des ordres AlpacaDans le premier message que vous envoyez
Binance, Bybit, OKX, Kraken, Coinbase, Coinbase ExchangeNulle part : les flux publics n’en ont pas besoin, et le flux privé de Kraken prend le jeton que renvoie son appel GetWebSocketsToken
Passerelle DiscordDans le premier message que vous envoyez : le d.token d’IDENTIFY
Slack Socket Mode, Twitch EventSub, RingCentral, Vapi, console HetznerNulle part : ouvrez l’URL que le service a renvoyée, ticket compris
Railway, Render, Koyeb, Daytona, Mattermost, ntfy, Grafana CloudDans la poignée de main
Browserless, Steel, BrowserbaseDans la query : token, apiKey

Certains services font tourner leur WebSocket sur un hôte à eux : gateway.discord.gg, agent.deepgram.com, ws.finnhub.io, le wss-….slack.com d’un ticket Socket Mode. Un tel hôte s’ouvre par la connexion de ce service, en premier segment du chemin : wss://mcp.sallyport.cloud/p/{binding_id}/gateway.discord.gg/?v=10&encoding=json. Seul un WebSocket l’atteint.

Quand la clé va dans le premier message, envoyez le message que documente le fournisseur avec "{{credential}}" à la place de la clé, ou sans ce champ. Sallyport y écrit la clé avant que le message arrive au service, et l’appel dans Activité montre votre message tel que vous l’avez envoyé.

JavaScript · OpenAI Realtime, une réponse en texte
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 · transcription en direct avec 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' }))
}

Refus

Un refus est du JSON : {"error": {"code": …, "instruction": …}}, avec à côté les champs dont son code a besoin. instruction est une phrase en anglais sur la suite à donner.

Quatre codes sont des résultats, pas des erreurs, et arrivent comme un objet simple sans l’enveloppe error : APPROVAL_PENDING, ACCESS_REQUEST_CREATED, ALREADY_EXECUTED et UPSTREAM_ERROR. Sur /p/, un corps UPSTREAM_ERROR est la réponse 424 quand le service n’a jamais répondu. Un service qui répond par une erreur vous envoie sa propre réponse.

Statut et codeCe qui s’est passé
401 AUTH_FAILEDLe jeton manque, est faux, révoqué ou valable pour /mcp seulement. WWW-Authenticate indique à un client OAuth où obtenir le bon.
403 SOURCE_IP_NOT_ALLOWEDLa requête vient d’une adresse hors de la liste autorisée de l’agent.
423 SUSPENDEDL’agent, son espace de travail ou l’organisation est en pause.
404 ACCESS_REQUEST_CREATEDL’agent ne peut pas utiliser cette connexion : pas d’accès, connexion en pause ou ID inconnu. Montrez approve_url à une personne, puis répétez l’appel.
403 POLICY_DENYLa règle de l’agent pour cette connexion refuse cette classe d’appel.
404 CALL_NOT_FOUNDLe X-Sallyport-Call-Id ou l’appel interrogé appartient à un autre agent ou n’existe pas. Faites un nouvel appel.
400 BAD_REQUESTreason dit quoi corriger : missing_intent, intent_too_long, body_too_large avec max_bytes, missing_host, query_in_path, mcp_binding, database_binding, email_binding, email_operation, sftp_operation, websocket_handshake et d’autres.
400 PATH_OUT_OF_PREFIXLe chemin sort de la limite de chemin de la connexion, par exemple via ...
Codes 409IDEMPOTENCY_CONFLICT, IN_FLIGHT, CALL_CONFLICT ou APPROVAL_SUPERSEDED. L’instruction dit quoi faire.
426 UPGRADE_NOT_SUPPORTEDUn upgrade vers un autre protocole que WebSocket.
426 WEBSOCKET_REQUIREDUne interrogation d’une session WebSocket approuvée. Rouvrez le WebSocket avec X-Sallyport-Call-Id.
429 RATE_LIMITEDUne limite de fréquence de l’agent ou de la connexion. Attendez Retry-After et répétez.
Codes 424EGRESS_DENIED, SERVICE_NOT_CONNECTED, UPSTREAM_TOO_LARGE, OUTCOME_UNKNOWN ou UPSTREAM_ERROR. Lisez l’instruction avant de répéter : OUTCOME_UNKNOWN signifie que le service a pu agir.
503 UNAVAILABLE, 500 INTERNALUne panne côté Sallyport. Répétez un 503 après Retry-After.

Décrire la requête dans le corps

Un client qui ne peut pas fixer la méthode ou le chemin peut envoyer en POST une description avec Content-Type: application/vnd.sallyport.request+json. Chaque champ est facultatif et remplace cette partie de la requête.

Shell · un DELETE décrit dans 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/"

Les champs sont method, path, query, headers, body, intent et idempotency_key. path n’accepte ni ? ni # : les paramètres vont dans query. Un body JSON part en JSON, une chaîne part telle quelle.

Une réponse peut aller jusqu’à 512 MiB. La page de la connexion montre sa propre limite sous Limites, comme Plus grande réponse du service. La réponse est transmise à mesure qu’elle arrive, donc une réponse qui dépasse la limite est coupée. Le statut et les en-têtes sont déjà envoyés, et le corps s’arrête avant la fin. Une interrogation de l’appel nommé dans X-Sallyport-Call-Id montre alors UPSTREAM_TOO_LARGE.