
Skill
use-ai-gateway
integrate AI Gateway models and tools
Description
Discover the models and MCP tool servers registered in an AI Gateway, retrieve a credential, and integrate them into any application — call a model over its exact runtime protocol (OpenAI Chat Completions, OpenAI Responses, or Anthropic Messages), connect to MCP tool servers, or scaffold a standalone agent. Use this whenever a developer wants to use an AI Gateway's models and/or tools from the app they are building.
SKILL.md
Use an AI Gateway (models & MCP tools)
Invoked as
/ai-gateway:use-ai-gatewaywhen installed via the plugin. It is also invoked automatically by the/ai-gateway:discoverand/ai-gateway:buildcommands.
This skill is coding-agent–agnostic: any assistant that can read this file and run shell commands can follow it.
Use this skill when a developer wants to use the models and/or MCP tools registered in an existing AI Gateway from their application — whatever they are building (a web app, backend service, script, notebook, data pipeline, or a standalone agent). This single skill takes the user all the way from discovery to working, integrated code:
- Discover the models and MCP tools registered in the gateway.
- Select the ones that match the user's intent (and confirm with them).
- Retrieve the credential needed to call them.
- Integrate them into the user's application, choosing the path that fits what they're building:
- Call a model over the gateway's OpenAI-compatible passthrough (any language, framework, or raw HTTP).
- Connect MCP tools from an MCP-capable client/app.
- Scaffold a standalone agent (GitHub Copilot SDK) if the user wants a ready-to-run agent project.
Scope (consumption only)
This skill is strictly for developers consuming an existing AI Gateway. It is read-only against the gateway and only generates client code that calls already-registered models and tools.
- ✅ Allowed: list models, list tools/MCP servers, read an existing API key, generate integration/client/agent code.
- ❌ Not allowed: creating, provisioning, updating, or deleting gateways, models, tools, connections, products, or any other resource. Never issue ARM
PUT/PATCH/DELETEcalls. If a user asks to create or provision a gateway or model, tell them that is an administrator task done in the AI Gateway Portal and is out of scope for this skill.
Part 1 — Discover & select assets
Parameters
Ask the user for (or confirm) these before calling the API:
- gatewayResourceId — the ARM resource ID of the user's gateway. It is one of two APIM resource types:
/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.ApiManagement/service/<gateway-name>or.../Microsoft.ApiManagement/aigateways/<gateway-name>. Both expose the same sub-resources (workspaces/default/models,workspaces/default/toolServers, andapiKeysat the resource root), so every template below works for either type. Keep the leading/exactly as ARM returns theid. The URL templates append it directly tohttps://management.azure.com(no slash in between), so the full URL resolves tohttps://management.azure.com/subscriptions/.... Don't add a slash before a leading-slash id — that produces a doubled//subscriptions/...and the call fails.Discovery needs the resource id, not just a host. Listing models/tools/keys goes through the ARM control plane, which is keyed by
gatewayResourceId. If the user gives you only a runtime host, resolve the id first (e.g.az resource list --name <name>or the Azure Portal) or ask for it — you cannot list assets from a host alone. - apiVersion —
2025-09-01-preview
Every ARM call must be authenticated with an Azure Resource Manager bearer token:
Authorization: Bearer <token>
Retrieve the token with:
az account get-access-token --query accessToken -o tsv
0. Resolve the runtime host
Discovery uses the ARM control plane (keyed by gatewayResourceId), but the model passthrough and MCP endpoints are called on the gateway's runtime host. Read the host from the resource itself rather than guessing it from the name:
GET https://management.azure.com{gatewayResourceId}?api-version={apiVersion}
Use properties.gatewayUrl (e.g. https://<name>.<region>.ai.gateway-current.azure.com) as <ai-gateway-host> for every runtime URL below. If properties.gatewayUrl is empty, fall back to https://<properties.frontend.defaultHostname>; never fabricate <name>.azure-api.net from the resource name. All runtime paths are served under the workspace segment /default/ (the Default workspace — the only one today). So the model runtime base is <ai-gateway-host>/default/models and each MCP endpoint is <ai-gateway-host>/default/toolservers/<tool-server-name>/mcp. Omitting the /default/ segment returns 404 Resource not found.
The exact suffix after /default/models depends on the model's protocol, which you read from each model's properties.supportedEndpoints (see §1) — most commonly the OpenAI-compatible /openai/v1/chat/completions, but a model may instead expose /openai/v1/responses (OpenAI Responses) or /anthropic/v1/messages (Anthropic Messages). Build the runtime URL as <ai-gateway-host>/default/models{supportedEndpoint} — don't assume every model is OpenAI Chat Completions.
One-shot discovery (copy-paste)
This performs the whole of Part 1 in one block. Set RID to the gatewayResourceId (keep its leading /); requires az and jq:
RID="/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.ApiManagement/service/<name>"
VER="2025-09-01-preview"
TOKEN=$(az account get-access-token --resource https://management.azure.com --query accessToken -o tsv)
B="https://management.azure.com$RID"
AUTH=(-H "Authorization: Bearer $TOKEN")
# Runtime host (used for every model/MCP call below)
HOST=$(curl -s "${AUTH[@]}" "$B?api-version=$VER" | jq -r '.properties.gatewayUrl')
# Models -> use .properties.deployment.modelName as the model id (exact dots/casing)
curl -s "${AUTH[@]}" "$B/workspaces/default/models?api-version=$VER" \
| jq -r '.value[] | "\(.properties.deployment.modelName)\t\(.properties.supportedEndpoints | join(","))"'
# MCP tool servers
curl -s "${AUTH[@]}" "$B/workspaces/default/toolServers?api-version=$VER" | jq -r '.value[].name'
# API key: list keys, then read the first active key's secret (name is commonly "master" or "default")
KEYNAME=$(curl -s "${AUTH[@]}" "$B/apiKeys?api-version=$VER" | jq -r '.value[0].name')
KEY=$(curl -s -X POST "${AUTH[@]}" -H "Content-Length: 0" \
"$B/apiKeys/$KEYNAME/listSecrets?api-version=$VER" \
| jq -r '.primaryKey // .properties.primaryKey // .primaryValue // .properties.primaryValue // .value')
# Smoke-test a model over the passthrough — note the /default/ workspace segment and the Api-Key header
curl -s "$HOST/default/models/openai/v1/chat/completions" \
-H "Api-Key: $KEY" -H "Content-Type: application/json" \
-d '{"model":"<modelName>","messages":[{"role":"user","content":"ping"}],"max_tokens":5}'
The individual steps and their gotchas are documented below.
1. Discover models
List the models registered in the gateway workspace:
GET https://management.azure.com{gatewayResourceId}/workspaces/default/models?api-version={apiVersion}
Use properties.deployment.modelName as the model identifier in every gateway call — not the ARM resource name and not properties.displayName. This is the single most common cause of failures, so get it right:
properties.deployment.modelNameis the exact string the OpenAI passthrough accepts in the request body, e.g.gpt-5.4-nanoorDeepSeek-V3.2. It preserves dots and original casing.- The ARM resource
nameis a sanitized version of that value with dots and other characters replaced by dashes (e.g.gpt-5-4-nano). Some gateways accept the sanitizednametoo, but others reject it with a misleadingunknown_modelerror — so always sendproperties.deployment.modelNameto be safe, and don't "correct" a working call back to the ARMname. properties.displayNameis a human-friendly label and is also rejected by the passthrough.
For each model, read properties.deployment.modelName and carry that exact string (same dots, same casing, same punctuation) through to the agent's model= parameter. If properties.deployment.modelName is missing for some reason, fall back to confirming the accepted ID by probing POST {gateway-host}/default/models/openai/v1/chat/completions with a tiny payload rather than guessing from the ARM name.
Match models to the user's use case based on the model name/description (e.g. a GPT-class model for chat, an embedding model for RAG). Suggest the best match as the default but let the user pick another.
Read each model's protocol from properties.supportedEndpoints — don't assume every model is OpenAI Chat Completions. This array holds the exact runtime operation path(s) the model accepts; select the one matching your integration and build the runtime URL as <ai-gateway-host>/default/models{supportedEndpoint} (preserve the returned value exactly, including any provider prefix like /openai/v1 or /anthropic/v1). The recognized agent/chat protocols are:
| Endpoint suffix | Protocol | Client |
|---|---|---|
/chat/completions | OpenAI Chat Completions | OpenAI-compatible client (Chat Completions mode) |
/responses | OpenAI Responses | OpenAI-compatible client (Responses mode) |
/messages | Anthropic Messages | Anthropic-compatible client (Messages mode) |
Most gateways today expose /openai/v1/chat/completions, which is why the examples below default to it — but if the selected model's supportedEndpoints shows /responses or /messages instead, use that protocol's client and request shape (see Path A). If a model exposes only non-agent endpoints (embeddings, images, audio, etc.), don't scaffold a chat agent against it — explain the mismatch and let the user pick a different model.
2. Discover tools (MCP tool servers)
Tools are registered as tool servers in the gateway workspace. Each tool server is a federated MCP host exposed at a single MCP endpoint on the gateway. List them:
GET https://management.azure.com{gatewayResourceId}/workspaces/default/toolServers?api-version={apiVersion}
Each item's name is the tool server name. Build its MCP endpoint URL as https://{gateway-host}/default/toolservers/{toolServerName}/mcp (note the /default/ workspace segment — without it the gateway returns 404). properties.mcpEndpointUrl is frequently empty in the ARM response, so do not rely on it — construct the URL from the host and tool server name, and only use properties.mcpEndpointUrl if it is non-empty. Filter to the tools relevant to the user's request (examine names and descriptions for keywords — e.g. for shipping, look for a "calculator" tool). Then stop and ask the user: "I found these tools for your use case. Which would you like to use?" List them clearly with brief descriptions and endpoints, and allow selecting all, some, or none.
Verify the tool server actually exposes tools before wiring it in. A tool server can be registered but expose zero tools (e.g. a federated OpenAPI/MCP source that failed to sync). Confirm with an MCP handshake against the constructed URL, using the
Api-Keyheader andAccept: application/json, text/event-streamon every request:POSTaninitializerequest, capture theMcp-Session-Idresponse header,POSTanotifications/initializedmessage with that session id, thenPOSTatools/listrequest (also carrying the session id). Iftools/listreturns an empty array, tell the user that server has no usable tools right now (a gateway-side configuration issue, not a client bug) and let them pick a different one rather than scaffolding an agent that can't call anything.
3. Retrieve a credential
The model passthrough and MCP tool servers are called with a gateway API key — the same key works for both.
- List the available keys (keys live at the gateway level, not the workspace):
GET https://management.azure.com{gatewayResourceId}/apiKeys?api-version={apiVersion} - Pick a key — use the first key whose
properties.stateisactive. The name is commonlymasterordefault; don't hardcode it, read it from the list response. - Retrieve the secret:
The key is normally in
POST https://management.azure.com{gatewayResourceId}/apiKeys/{keyName}/listSecrets?api-version={apiVersion}primaryKey:Backend versions have returned a few response shapes, so read the first non-empty value from, in order:{ "primaryKey": "2e41...", "secondaryKey": "365..." }primaryKey,properties.primaryKey,primaryValue,properties.primaryValue, thenvalue(whentarget == "Primary"), falling back to the equivalentsecondary*fields. If none is present, the caller likely isn't authorized tolistSecrets— stop and direct them to an administrator rather than creating or rotating a key.
The credential is passed in the Api-Key header for both the model passthrough and the MCP tool servers.
Discovery-only path: If the user only wants to explore and select assets (models/tools) and does not want to integrate them yet, you can stop here. Present the discovered assets and selected credential, and skip Part 2. Code generation is optional.
Part 2 — Integrate into your application
By now you have (a) the selected model(s) and/or MCP tool server(s) and (b) the gateway API key. Now wire them into whatever the user is building.
First, pick the integration path — ask the user what they're building if it isn't already clear, then follow the matching path (they can combine A and B):
- Path A — Call a model. The user wants their app/script/service to send prompts to a model. Works from any language or raw HTTP via the gateway's OpenAI-compatible passthrough. Most common for existing apps.
- Path B — Connect MCP tools. The user wants their app or MCP-capable client to call the gateway's registered MCP tool servers.
- Path C — Scaffold a standalone agent. The user wants a ready-to-run agent project (GitHub Copilot SDK) that combines models and tools.
Whichever path you take, integrate into the user's existing project when they have one (respect its language, framework, and conventions) rather than forcing a new scaffold. Path C's scaffold is only for users who explicitly want a fresh standalone agent.
Shared rules (all paths):
- Read every credential from environment variables (e.g.
AI_GATEWAY_API_KEY) — never hardcode secrets. See Shared: credentials & project hygiene below. - The gateway authenticates both models and MCP tool servers with the same key, passed in the
Api-Keyrequest header. - Client key vs. backend managed identity are different things. A model provider or tool server may use the gateway's managed identity to authenticate to its upstream (that's gateway-to-backend auth, transparent to your app). Don't try to fetch or supply that upstream credential, and don't replace your
Api-Keyheader with a managed-identity/bearer token — your app always authenticates to the gateway runtime with the gatewayApi-Key.
Path A — Call a model
Most AI Gateway models expose an OpenAI-compatible endpoint, so the examples below default to it. First confirm the selected model's protocol from its properties.supportedEndpoints (see §1) and derive the client base URL from the exact endpoint by stripping the operation suffix:
| Protocol (endpoint suffix) | Client base URL = <ai-gateway-host>/default/models + … | Client appends |
|---|---|---|
OpenAI Chat Completions (/chat/completions) | /openai/v1 (drop /chat/completions) | /chat/completions |
OpenAI Responses (/responses) | /openai/v1 (drop /responses) | /responses |
Anthropic Messages (/v1/messages) | the endpoint with /v1/messages removed (Anthropic SDK re-adds it) | /v1/messages |
Common case — OpenAI-compatible. Point any OpenAI-style client (or plain HTTP) at it:
- Base URL:
<ai-gateway-host>/default/models/openai/v1— e.g.https://my-gateway.westus2-01.ai.gateway-current.azure.com/default/models/openai/v1. This is the same endpoint the AI Gateway Portal advertises to consumers; clients append/chat/completions(or/responsesfor a Responses model). The/default/segment (the workspace) is required — dropping it returns404. - Auth header:
Api-Key: <gateway key>. Do not rely onAuthorization: Bearer— the gateway model passthrough rejects bearer-only auth, typically with a401("missing subscription key") or a misleadingunknown_modelerror. If your client insists on anapi_keyfield (many do), still set theApi-Keyheader explicitly. - Model identifier: use the selected model's
properties.deployment.modelName(e.g.gpt-5.4-nano) exactly — same dots, casing, and punctuation. Prefer it over the ARM resourcename(e.g.gpt-5-4-nano) ordisplayName, which may be rejected withunknown_modelon some gateways.
curl
curl "$AI_GATEWAY_HOST/default/models/openai/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "Api-Key: $AI_GATEWAY_API_KEY" \
-d '{
"model": "<properties.deployment.modelName>",
"messages": [{ "role": "user", "content": "Hello!" }]
}'
Python (openai SDK)
import os
from openai import OpenAI
client = OpenAI(
base_url=f"{os.environ['AI_GATEWAY_HOST']}/default/models/openai/v1",
api_key=os.environ["AI_GATEWAY_API_KEY"],
# The gateway authenticates via the Api-Key header, not Authorization: Bearer.
default_headers={"Api-Key": os.environ["AI_GATEWAY_API_KEY"]},
)
resp = client.chat.completions.create(
model="<properties.deployment.modelName>", # e.g. "gpt-5.4-nano"
messages=[{"role": "user", "content": "Hello!"}],
)
print(resp.choices[0].message.content)
TypeScript / JavaScript (openai SDK)
import OpenAI from "openai";
const client = new OpenAI({
baseURL: `${process.env.AI_GATEWAY_HOST}/default/models/openai/v1`,
apiKey: process.env.AI_GATEWAY_API_KEY!,
// The gateway authenticates via the Api-Key header, not Authorization: Bearer.
defaultHeaders: { "Api-Key": process.env.AI_GATEWAY_API_KEY! },
});
const resp = await client.chat.completions.create({
model: "<properties.deployment.modelName>", // e.g. "gpt-5.4-nano"
messages: [{ role: "user", content: "Hello!" }],
});
console.log(resp.choices[0].message.content);
The passthrough is OpenAI-compatible, so streaming, tools/function-calling, and other OpenAI request fields work as usual — just keep the
Api-Keyheader and the exactmodelName.
Other protocols (only when the model's supportedEndpoints says so)
If the selected model exposes /responses or /anthropic/v1/messages instead of /chat/completions, use that protocol's request shape (same Api-Key header, same exact modelName, same /default/models prefix):
# OpenAI Responses model (supportedEndpoints contains /openai/v1/responses)
curl "$AI_GATEWAY_HOST/default/models/openai/v1/responses" \
-H "Content-Type: application/json" -H "Api-Key: $AI_GATEWAY_API_KEY" \
-d '{"model":"<properties.deployment.modelName>","input":"Hello!"}'
# Anthropic Messages model (supportedEndpoints contains /anthropic/v1/messages)
curl "$AI_GATEWAY_HOST/default/models/anthropic/v1/messages" \
-H "Content-Type: application/json" -H "Api-Key: $AI_GATEWAY_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"<properties.deployment.modelName>","max_tokens":16,"messages":[{"role":"user","content":"Hello!"}]}'
For SDKs, set the base URL per the derivation table above and keep the Api-Key header: the OpenAI SDK's responses.create(...) targets a Responses model, and the Anthropic SDK with base_url = <messages endpoint minus /v1/messages> plus default_headers={"Api-Key": ...} targets a Messages model. Don't point an OpenAI Chat Completions client at a /responses- or /messages-only model (and vice versa) — the protocol must match the endpoint.
Path B — Connect to MCP tool servers
Each registered MCP tool server is reachable at its own MCP endpoint (captured during discovery), authenticated with the same gateway key via the Api-Key header. Use it from any MCP client:
- Endpoint: the tool server's MCP URL from discovery (e.g.
<ai-gateway-host>/default/toolservers/<name>/mcp). - Transport: streamable HTTP (
type: "http"). - Auth header:
Api-Key: <gateway key>.
To sanity-check a tool server manually, run the full MCP handshake — initialize, then notifications/initialized, then tools/list — carrying the Mcp-Session-Id returned by initialize on the follow-up calls:
# 1. initialize — capture the Mcp-Session-Id response header
SID=$(curl -s -D - -o /dev/null "$TOOL_ENDPOINT" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-H "Api-Key: $AI_GATEWAY_API_KEY" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"1.0"}}}' \
| awk -F': ' 'tolower($1)=="mcp-session-id"{print $2}' | tr -d '\r')
# 2. notifications/initialized
curl -s "$TOOL_ENDPOINT" -H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" -H "Api-Key: $AI_GATEWAY_API_KEY" \
${SID:+-H "Mcp-Session-Id: $SID"} \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null
# 3. tools/list — an empty "tools" array means the server exposes no usable tools
curl -s "$TOOL_ENDPOINT" -H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" -H "Api-Key: $AI_GATEWAY_API_KEY" \
${SID:+-H "Mcp-Session-Id: $SID"} \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
To wire it into an application, configure your MCP client with the endpoint URL, http transport, and the Api-Key header — for example, in a client that reads an mcp.json/mcpServers map:
{
"mcpServers": {
"<tool-name>": {
"type": "http",
"url": "<tool-endpoint>",
"headers": { "Api-Key": "${AI_GATEWAY_API_KEY}" }
}
}
}
Adapt the exact shape to whatever MCP client/library the user's app uses (the essentials are always: endpoint URL, HTTP transport, and the Api-Key header). Native config locations for common coding agents:
- Claude Code — the
mcpServersJSON above is exactly the shape of a project-scoped.mcp.json. Claude Code expands${AI_GATEWAY_API_KEY}from the environment in HTTPheaders. Runclaude mcp list(or/mcp) and approve the project server when prompted. - OpenAI Codex — add to
~/.codex/config.toml(or a trusted project.codex/config.toml), passing the key by env-var reference so the secret isn't written into config:Verify with[mcp_servers.<tool-server-name>] url = "<ai-gateway-host>/default/toolservers/<tool-server-name>/mcp" env_http_headers = { "Api-Key" = "AI_GATEWAY_API_KEY" }codex mcp listor/mcp. - Other MCP-capable agents — consult the agent's current CLI help or config schema for its remote/HTTP MCP entry rather than guessing field names; the essentials are the same three: endpoint URL, HTTP transport,
Api-Keyheader.
If the user is building an agent that needs these tools, prefer Path C, which wires models and MCP tools together.
Path C — Scaffold a standalone agent (GitHub Copilot SDK)
Use this path when the user explicitly wants a runnable standalone agent project that combines the selected model and MCP tools.
C.1 Choose language
Ask the user which language they prefer. Default to Python if not specified. Supported:
- Python (default)
- TypeScript/JavaScript
C.2 Generate agent code
Generate the agent code using the GitHub Copilot SDK (code, docs, examples: https://github.com/github/copilot-sdk).
Required SDK version:
github-copilot-sdk >= 1.0.0. The agent authenticates the BYOK model and the MCP tool servers by passing the gateway key in custom request headers (Api-Key). SDK versions older than 1.0.0 silently drop provider/custom headers, so the gateway rejects the call with a misleadingunknown_modelerror. Always pin>= 1.0.0inrequirements.txt/package.jsonand in any install command.
The generated code must:
- Use the GitHub Copilot SDK correctly for the chosen language (default to Python, unless the user specifies otherwise). Require
github-copilot-sdk >= 1.0.0— older versions drop the custom headers used to authenticate the gateway model and tools - Configure MCP tool connections to the selected AI Gateway tool servers via
mcp_serversin the session config, usingtype: "http"with the tool server's MCP endpoint URL - Authenticate tool servers with the gateway API key passed in the
Api-Keyheader via theheadersconfig - Configure model access through a BYOK
providerin the session config - Authenticate models with the same gateway API key passed in the
Api-Keyheader via the provider'sheadersconfig — not theapi_keyfield alone. The SDK sendsapi_keyas anAuthorization: Bearerheader, which the AI Gateway model passthrough rejects with a misleadingunknown_modelerror. Set theApi-Keyheader explicitly (keepapi_keytoo, since the SDK requires a non-empty value) - Use model provider
type: "openai"(the AI Gateway exposes an OpenAI-compatible passthrough, where the model is selected by name in the request body) - Construct the model
base_urlas the AI Gateway's unified model passthrough:<ai-gateway-host>/default/models/openai/v1— for examplehttps://my-gateway.westus2-01.ai.gateway-current.azure.com/default/models/openai/v1. This is the same endpoint the AI Gateway Portal advertises to consumers (the SDK appends/chat/completions). The/default/workspace segment is required — dropping it returns404 - Precisely specify the model parameter and match it exactly to the selected model's
properties.deployment.modelName(e.g.gpt-5.4-nano) — same dots, same casing, same punctuation. Prefer it over the ARM resourcename(e.g.gpt-5-4-nano) ordisplayName, which may be rejected withunknown_modelon some gateways - Include
"on_permission_request": PermissionHandler.approve_allin the session config (importPermissionHandlerfromcopilot) - Pass the session config to
create_sessionas keyword arguments (e.g.create_session(model=..., provider=..., mcp_servers=...)), like in the example below - Read all credentials from environment variables — never hardcode secrets
- Load environment variables from a
.envfile usingpython-dotenv(Python) ordotenv(TypeScript) - Use
send_and_wait()with the prompt as a positional string to get the agent's response, then always print the response content or the error — the user must see output - Handle errors gracefully with full tracebacks for debugging
- Include a clear main entry point and example usage
- Match the code example below as closely as possible
Python Example Structure
import asyncio
import os
from dotenv import load_dotenv
from copilot import CopilotClient, PermissionHandler
load_dotenv()
async def main():
client = CopilotClient()
await client.start()
try:
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
model="<selected-model>", # e.g. "gpt-4o"
# BYOK provider — points to the AI Gateway unified model passthrough.
# The gateway authenticates the model passthrough via the `Api-Key`
# header, so pass the key in `headers`. The `api_key` field alone is
# sent as `Authorization: Bearer`, which the gateway rejects with
# `unknown_model`.
provider={
"type": "openai",
"base_url": "<ai-gateway-host>/default/models/openai/v1",
"api_key": os.environ["AI_GATEWAY_API_KEY"],
"headers": {
"Api-Key": os.environ["AI_GATEWAY_API_KEY"],
},
},
# MCP tool servers from AI Gateway
mcp_servers={
"<tool-name>": {
"type": "http",
"url": "<tool-endpoint>",
"headers": {
"Api-Key": os.environ["AI_GATEWAY_API_KEY"],
},
"tools": ["*"],
},
# Add more tools as needed
},
)
def on_event(event):
event_type = event.type.value
if event_type == "tool.execution_start":
print(f" [tool call: {event.data.tool_name}]")
elif event_type == "error":
msg = getattr(event.data, "message", str(event))
print(f" [error: {msg}]")
session.on(on_event)
reply = await session.send_and_wait("<user prompt>")
if reply:
print("\n=== Agent Response ===")
print(reply.data.content if reply.data.content else "(empty response)")
else:
print("\n=== No response received from agent ===")
await session.disconnect()
except Exception as e:
print(f"\nFailed to run agent: {e}")
import traceback
traceback.print_exc()
finally:
await client.stop()
if __name__ == "__main__":
asyncio.run(main())
C.3 Create project files
After generating the agent code, create a complete, self-contained project the user can download and run as-is. See Shared: credentials & project hygiene below for .env, .env.example, and .gitignore, plus:
requirements.txt(Python) — pin the agent's dependencies so the project installs in one command:github-copilot-sdk>=1.0.0 python-dotenv
(For TypeScript, generate apackage.jsonwith the equivalent dependencies —github-copilot-sdkat>=1.0.0— and astartscript.)README.md— a short, self-contained guide so the downloaded project runs without this chat. Include:- what the agent does and which gateway model + tool servers it uses (by name)
- prerequisites (Python 3.10+, the Copilot CLI,
github-copilot-sdk >= 1.0.0) - install steps (
pip install -r requirements.txt) - how to set credentials (copy
.env.exampleto.env, or use the pre-filled.env) - the run command and the expected output
C.4 Provide setup instructions
After generating the code, provide:
- Required package installation commands:
pip install "github-copilot-sdk>=1.0.0" python-dotenv(the>= 1.0.0pin is mandatory — older SDKs drop the customApi-Keyheaders and the gateway returnsunknown_model) - Copilot CLI must be installed separately (the SDK communicates with it via JSON-RPC)
- Environment variable setup:
AI_GATEWAY_API_KEY— the gateway API key from discovery (passed via theApi-Keyheader for tool servers and via the BYOK providerapi_keyfor the model)
- How to run the agent
C.5 Run and verify
Don't stop at "here's the code." Run the agent once and confirm the wiring actually works, then report the outcome to the user:
- Run it (e.g.
python agent.py) and read the output. - Distinguish config failures from benign backend conditions. Treat these as a healthy end-to-end wiring (the gateway accepted the request) — report success and move on:
429 ... exceeded rate limitor quota errors from the model- empty/slow responses caused by the upstream model, not the client
- Treat these as real bugs to fix before handing off:
unknown_model→ wrong model identifier (used ARMname/displayNameinstead ofproperties.deployment.modelName), a protocol mismatch (e.g. a Chat Completions client pointed at a/responses- or/messages-only model), or an SDK older than1.0.0dropping theApi-KeyheaderKeyError: 'AI_GATEWAY_API_KEY'→.envnot loaded, usually a BOM (see the.envnote below)401/403→ wrong or missingApi-Keyheader
- If you can't get a clean model response because of a backend
429/quota, say so explicitly and note that everything up to the model was validated, rather than implying the full round-trip succeeded.
Shared: credentials & project hygiene
Whenever you write credentials or scaffold files into the user's project (any path), keep secrets safe:
.env— populated with the actual key retrieved during discovery:AI_GATEWAY_API_KEY=<actual gateway API key from discovery>Security note: this writes a live gateway secret in cleartext to disk. The
.gitignore(below) keeps it out of source control, but warn the user that the file holds a real key and to rotate it in the AI Gateway Portal if it is ever exposed or shared.Write
.envas UTF-8 without a BOM.python-dotenv(anddotenvfor Node) does not strip a leading byte-order mark, so a BOM makes the first variable load as\ufeffAI_GATEWAY_API_KEYand the app crashes withKeyError: 'AI_GATEWAY_API_KEY'. This bites on Windows specifically: PowerShell'sSet-Content -Encoding utf8andOut-Fileadd a BOM. Write the file with a BOM-free encoder instead, e.g.:[System.IO.File]::WriteAllText("$PWD\.env", "AI_GATEWAY_API_KEY=$key`n", (New-Object System.Text.UTF8Encoding($false)))or simply create the file through your editor/agent file-write tool, which does not add a BOM. After writing, sanity-check that the first byte is not
0xEF..env.example— a template with a placeholder value for documentation:AI_GATEWAY_API_KEY=your-gateway-api-key-here.gitignore— must include.envto prevent committing secrets:.env __pycache__/ *.pyc .venv/
More from Azure (Microsoft)
View publisherazure-arg-external-evaluation-policy-author
author and test Azure Resource Graph policies
azure-policy
Jul 12AzureComplianceGovernancePolicyazure-blueprints-migration
migrate Azure Blueprints to Template Specs
azure-blueprints
Jul 12AzureDeploymentInfrastructure as CodeMigrationapiview-feedback-resolution
resolve APIView feedback on Azure SDKs
azure-sdk-tools
Jul 12API DevelopmentAzureCode ReviewDocumentationazsdk-common-live-and-recorded-tests
deploy resources and run Azure SDK tests
azure-sdk-tools
Jul 12AzureDeploymentSDKTestingazsdk-common-prepare-release-plan
manage Azure SDK release plan work items
azure-sdk-tools
Jul 12AzureGitHubProject ManagementSDKazsdk-common-sdk-release
release Azure SDK packages
azure-sdk-tools
Jul 12AzureCI/CDDeploymentSDK