DEV Community

Cover image for A Senior Engineer's Guide to Foundry IQ, MCP, and the OpenAI Agents SDK
Jubin Soni
Jubin Soni Subscriber

Posted on

A Senior Engineer's Guide to Foundry IQ, MCP, and the OpenAI Agents SDK

Most Foundry writeups assume you're all in on Microsoft's stack end to end, the Agent Framework for orchestration, the Foundry Agent Service for hosting, the Responses API wrapped in Microsoft's own client. That's a reasonable default, but it's not the only shape this can take. Microsoft Foundry hosts OpenAI's own models behind an OpenAI-compatible endpoint, and Foundry IQ exposes every Knowledge Base as a plain MCP server. Put those two facts together and you get a genuinely different setup: OpenAI's own Agents SDK, unmodified, orchestrating a model that happens to be running on Foundry, grounded by a Knowledge Base that happens to be Foundry IQ, with MCP as the only thing that has to agree between them.

This is a hands-on guide to building exactly that. Not because you should always prefer OpenAI's SDK over Microsoft's own tooling, but because knowing this path exists changes how you think about lock-in. If your orchestration layer is a thin, protocol-based client, swapping the model host or the knowledge layer underneath it is a config change, not a rewrite.

The mental model first

Three pieces, from three different places, held together by two protocols:

  • An OpenAI model, hosted on Microsoft Foundry. Foundry deploys OpenAI's models behind an endpoint that speaks the same wire format as OpenAI's own API, including the Responses API. Point any OpenAI-compatible client at that endpoint with a different base_url and it has no idea it's not talking to OpenAI directly.
  • A Foundry IQ Knowledge Base, doing the same job it always does: chunking, embedding, indexing, and agentic retrieval over your sources. What matters here is that every Knowledge Base speaks MCP natively. It doesn't care what called it.
  • The OpenAI Agents SDK, running as your orchestration layer, in your own process, not inside Foundry at all. It calls the Foundry-hosted model for reasoning and generation, and calls the Foundry IQ Knowledge Base as an MCP tool for grounding. Neither call requires Microsoft-specific code.

OpenAI Agents SDK, calling a Foundry model, grounded by Foundry IQ

The thing worth sitting with here: nothing about this setup is a workaround or an unsupported hack. Foundry explicitly documents the OpenAI SDK as the recommended client when you want maximum OpenAI compatibility or the lowest latency path to a Foundry-hosted model. Foundry IQ explicitly exposes MCP as a first-class interface, not an afterthought. This guide is just connecting two things that were each already built to be connected this way.

Prerequisites

You'll need:

  1. A Microsoft Foundry project with an OpenAI model deployed (a gpt-5.1 or similar deployment, created through the Foundry portal or the Foundry SDK).
  2. A Foundry IQ Knowledge Base already built and populated. If you haven't done this before, the short version is a Knowledge Source pointed at your data plus a Knowledge Base wrapping it, both created through azure-search-documents. The full walkthrough, chunking strategy, semantic configuration, and all, is worth its own read if you're starting from zero.
  3. Python 3.10+ with the OpenAI SDK and the Agents SDK installed.
pip install openai openai-agents azure-identity
Enter fullscreen mode Exit fullscreen mode

Step 1: point a plain OpenAI client at your Foundry deployment

Before bringing the Agents SDK into it, confirm the basic connection works with the plain OpenAI client. This is the part that trips people up the least, but it's worth isolating as its own step, because if it doesn't work here, nothing built on top of it will either.

from openai import OpenAI
from azure.identity import DefaultAzureCredential, get_bearer_token_provider

token_provider = get_bearer_token_provider(
    DefaultAzureCredential(), "https://ai.azure.com/.default"
)

client = OpenAI(
    base_url="https://YOUR-RESOURCE-NAME.services.ai.azure.com/api/projects/YOUR-PROJECT/openai",
    api_key=token_provider,
)

response = client.responses.create(
    model="gpt-5.1",
    input="Say hello in one sentence.",
)
print(response.output_text)
Enter fullscreen mode Exit fullscreen mode

Two things to get right here. The base_url is your Foundry project endpoint with /openai on the end, not the raw resource endpoint, and not the older /openai/v1/ Azure OpenAI-specific path (that one still works for Azure OpenAI resources, but the project endpoint is the current recommended shape for Foundry). And api_key accepts a callable token provider, not just a string, which is how Entra ID authentication slots in without you having to manually refresh anything.

Step 2: swap in the token provider and hand the client to the Agents SDK

The Agents SDK doesn't have its own concept of Azure authentication. It just needs an AsyncOpenAI client, and it doesn't care where that client points.

from openai import AsyncOpenAI
from agents import set_default_openai_client, set_tracing_disabled

async_client = AsyncOpenAI(
    base_url="https://YOUR-RESOURCE-NAME.services.ai.azure.com/api/projects/YOUR-PROJECT/openai",
    api_key=token_provider,
)
set_default_openai_client(async_client)
Enter fullscreen mode Exit fullscreen mode

One gotcha worth flagging immediately: the Agents SDK ships with built-in tracing that exports run traces to OpenAI's own platform dashboard by default. That's a sensible default when you're calling OpenAI directly, but it's an odd one once your model calls are routed through Foundry instead, since your traces would still be leaving through a separate, OpenAI-direct path that doesn't share your Foundry project's auth or data boundary. If that matters for your compliance posture, disable it or point it at your own collector:

set_tracing_disabled(True)
# or, to keep tracing but redirect it, register a custom trace processor instead
Enter fullscreen mode Exit fullscreen mode

This is easy to miss because nothing breaks if you leave it on. It just quietly sends run metadata somewhere your Foundry-hosted setup otherwise never touches.

Step 3: connect to the Knowledge Base over MCP

Every Foundry IQ Knowledge Base exposes itself at a predictable MCP endpoint. The Agents SDK's MCPServerStreamableHttp class is built for exactly this kind of self-managed, HTTP-based MCP server.

from agents.mcp import MCPServerStreamableHttp, create_static_tool_filter

kb_server = MCPServerStreamableHttp(
    name="foundry-iq-kb",
    params={
        "url": "https://YOUR-SEARCH-SERVICE.search.windows.net/knowledgebases/team-kb/mcp?api-version=2026-05-01-preview",
        "headers": {"api-key": "YOUR-SEARCH-ADMIN-KEY"},
        "timeout": 15,
    },
    cache_tools_list=True,
    tool_filter=create_static_tool_filter(allowed_tool_names=["knowledge_base_retrieve"]),
)
Enter fullscreen mode Exit fullscreen mode

cache_tools_list=True is worth defaulting to here. A Knowledge Base publishes exactly one tool, knowledge_base_retrieve, and that isn't going to change between requests, so there's no reason to pay a tools/list round trip on every single agent turn. The tool_filter is mostly redundant given there's only one tool to begin with, but it's cheap insurance if the Knowledge Base ever grows a second tool you don't want this particular agent touching.

Step 4: build the agent and run it

With the client and the MCP server both wired up, the agent itself is short.

import asyncio
from agents import Agent, Runner

async def main():
    async with kb_server as server:
        agent = Agent(
            name="support-agent",
            instructions=(
                "Answer questions using the knowledge_base_retrieve tool. "
                "Always call it before answering. Preserve [ref_id:N] citations "
                "from the tool's response in your final answer."
            ),
            model="gpt-5.1",
            mcp_servers=[server],
        )
        result = await Runner.run(agent, "What's our current rate limit on the export API?")
        print(result.final_output)

asyncio.run(main())
Enter fullscreen mode Exit fullscreen mode

The model string here is the Foundry deployment name, not an OpenAI model ID, since every call now routes through the client you registered in Step 2. If you want to stream the response instead of waiting for the full turn, Runner.run_streamed gives you the same event-based streaming interface regardless of which backend is actually generating the tokens:

result = Runner.run_streamed(agent, "What's our current rate limit on the export API?")
async for event in result.stream_events():
    if event.type == "raw_response_event" and hasattr(event.data, "delta"):
        print(event.data.delta, end="", flush=True)
Enter fullscreen mode Exit fullscreen mode

Nothing in either of these two blocks is Foundry-specific or Azure-specific. That's the point. The vendor-specific work all happened in Steps 1 through 3, in the client and connection setup, not in how you define or run the agent.

Where the credentials actually live

Where the OpenAI Agents SDK needs a credential

Two separate credentials are doing two separate jobs here, and it's worth being precise about which is which, because they fail differently.

The model credential is whatever you passed as api_key on the AsyncOpenAI client, a Foundry project token from DefaultAzureCredential, or a static API key if you're using key-based auth on the resource. This is checked on every responses.create() call the Agents SDK makes internally when the agent reasons or generates a final answer. Scope this to the project, not the whole Foundry resource, using the same RBAC roles you'd use for any other Foundry SDK client (Cognitive Services User is usually sufficient for inference-only access).

The Knowledge Base credential is the api-key header on the MCP server's params, and it's checked independently by Azure AI Search when the knowledge_base_retrieve tool gets called. These two credentials can be, and generally should be, scoped to completely different principals. A key that can call your Foundry model deployment shouldn't automatically be able to query every Knowledge Base on your Search service, and the reverse is just as true. If you're building anything past a prototype, put each behind its own least-privilege identity rather than reusing one Foundry project's admin key for both.

If your Knowledge Base sits over permission-sensitive content, this is also where the on-behalf-of pattern from Foundry IQ's own permission model applies unchanged: thread the requesting user's token through as an additional header on the MCP params, since the KB's enforcement of ingestionPermissionOptions doesn't know or care that the caller this time is the OpenAI Agents SDK instead of the Foundry Agent Service.

Production considerations before you commit

  • Decide on tracing deliberately, not by default. Leaving the Agents SDK's tracing on means run metadata leaves through an OpenAI-direct path that bypasses your Foundry project's boundary entirely. Turn it off or replace it with a custom processor as a first-day decision, not something you notice in a security review months later.
  • Cache the tool list, but know when to invalidate it. cache_tools_list=True avoids a redundant round trip, but if you ever change what a Knowledge Base exposes, which is rare but not impossible as Foundry IQ's MCP surface evolves, a long-lived process holding a stale cached tool list will keep calling the old shape until it's restarted.
  • Separate the model deployment's quota from the Knowledge Base's query load. These are billed and throttled independently. A burst of retrieval-heavy queries against the Knowledge Base won't show up as pressure on your model deployment's tokens-per-minute limit, and the reverse is also true, so alert on both rather than assuming one is a proxy for the other.
  • Pin the MCP API version. The api-version=2026-05-01-preview query parameter on the Knowledge Base's MCP URL is still a preview surface as of this writing. Track it the same way you'd track any other preview dependency, and don't assume a bare /mcp URL without a version pin will behave identically across a Foundry IQ update.
  • Keep the instructions honest about tool use. The Agents SDK does not force a tool call. If your instructions say "always call knowledge_base_retrieve" but the model decides a question doesn't need it, you'll get an ungrounded answer with no error. Log whether the tool was actually invoked on each run, not just what the final answer said, if grounding is a correctness requirement rather than a nice-to-have.

Where this leaves you

The interesting thing this setup demonstrates isn't that OpenAI's SDK can technically reach a Foundry endpoint. It's that both vendors built their integration points, an OpenAI-compatible inference endpoint on one side, an MCP-native Knowledge Base on the other, generally enough that they compose without either one knowing the other exists. That's a genuinely different bet than the usual platform story, where the value proposition is staying inside one vendor's tooling end to end. If you're already committed to the OpenAI Agents SDK for orchestration, whether for its tracing, its handoff model, or just team familiarity, you don't have to give that up to use Foundry-hosted models or Foundry IQ's retrieval layer. The protocol boundary is the only thing that has to hold, and both sides already built to it.

References

  1. Microsoft Learn. "Get started with Microsoft Foundry SDKs and endpoints." learn.microsoft.com/en-us/azure/foundry/how-to/develop/sdk-overview
  2. Microsoft Learn. "Use the Azure OpenAI Responses API." Microsoft Foundry. learn.microsoft.com/en-us/azure/foundry/openai/how-to/responses
  3. Microsoft Learn. "How to migrate from Azure AI Inference SDK to OpenAI SDK." Microsoft Foundry. learn.microsoft.com/en-us/azure/foundry/how-to/model-inference-to-openai-migration
  4. OpenAI. "Configuration." OpenAI Agents SDK documentation. openai.github.io/openai-agents-python/config
  5. Microsoft Learn. "What is Foundry IQ?" learn.microsoft.com/en-us/azure/foundry/agents/concepts/what-is-foundry-iq

Top comments (0)