Skip to main content

Documentation Index

Fetch the complete documentation index at: https://arize-ax.mintlify.dev/docs/llms.txt

Use this file to discover all available pages before exploring further.

Model Context Protocol (MCP) is an open protocol that lets agents call tools, fetch resources, and receive prompts from independent server processes. The openinference-instrumentation-mcp package is unusual: it doesn’t emit any spans of its own. Instead, it propagates OpenTelemetry context across the MCP wire protocol so that spans created independently in the client and the server join into a single unified trace. That means you need to install MCPInstrumentor alongside another instrumentor that does emit spans (one of the OpenInference framework or LLM instrumentors) in both processes, and have both write to the same Arize AX project.

Prerequisites

  • Python 3.10+
  • An Arize AX account (sign up)
  • An OPENAI_API_KEY from the OpenAI Platform (the example uses the OpenAI Agents SDK as the span-producing instrumentor)

Launch Arize AX

  1. Sign in to your Arize AX account.
  2. From Space Settings, copy your Space ID and API Key. You will set them as ARIZE_SPACE_ID and ARIZE_API_KEY below.

Install

Install the union of client and server dependencies into the same environment — the scripts below import each as needed:
pip install arize-otel \
  openinference-instrumentation-mcp \
  openinference-instrumentation-openai-agents \
  openai-agents mcp fastmcp openai

Configure credentials

export ARIZE_SPACE_ID="<your-space-id>"
export ARIZE_API_KEY="<your-api-key>"
export ARIZE_PROJECT_NAME="mcp-tracing-example"
export OPENAI_API_KEY="<your-openai-api-key>"
The client launches the server as a child process and forwards its environment, so both ends see the same ARIZE_PROJECT_NAME and write into the same Arize AX project.

Setup tracing

The setup is identical in both processes — register() then both instrumentors. Two important details for the server:
  1. Pass verbose=False to register(). The default Arize AX banner prints to stdout, which is the MCP wire-protocol channel; any stray output corrupts the protocol and the client disconnects.
  2. Run register() and the instrumentors before importing mcp.server.fastmcp so the patches catch the FastMCP module load.
import os

from arize.otel import register
from openinference.instrumentation.mcp import MCPInstrumentor
from openinference.instrumentation.openai_agents import OpenAIAgentsInstrumentor

tracer_provider = register(
    space_id=os.environ["ARIZE_SPACE_ID"],
    api_key=os.environ["ARIZE_API_KEY"],
    project_name=os.environ["ARIZE_PROJECT_NAME"],
)

MCPInstrumentor().instrument(tracer_provider=tracer_provider)
OpenAIAgentsInstrumentor().instrument(tracer_provider=tracer_provider)
print("Arize AX tracing initialized for MCP (client).")

Run MCP

The client and server are two separate Python files. The client uses MCPServerStdio to launch the server as a subprocess and pipe MCP traffic over stdin/stdout. You only run python client.py — it spawns python server.py automatically.
import asyncio
import os

# Set up tracing before any agents/mcp imports.
from instrumentation_client import tracer_provider

from agents import Agent, Runner
from agents.mcp import MCPServerStdio


async def main() -> None:
    # MCPServerStdio launches the server as a child process. Pass `env`
    # explicitly — MCP's stdio transport does not inherit the parent
    # environment by default, so without this the server can't read
    # ARIZE_*  / OPENAI_API_KEY.
    async with MCPServerStdio(
        name="Ocean Knowledge Server",
        params={
            "command": "python",
            "args": ["server.py"],
            "env": dict(os.environ),
        },
        client_session_timeout_seconds=30,
    ) as server:
        agent = Agent(
            name="Ocean Assistant",
            instructions=(
                "Use the explain_ocean tool to answer the user's question."
            ),
            mcp_servers=[server],
        )
        result = await Runner.run(
            starting_agent=agent,
            input="Why is the ocean salty? Answer in two sentences.",
        )
        print(result.final_output)


if __name__ == "__main__":
    asyncio.run(main())

Expected output

Arize AX tracing initialized for MCP (client).
The ocean is salty because rivers continuously dissolve mineral salts from rocks and soil and carry them to the sea, where they accumulate over millions of years. Water leaves the ocean through evaporation but the salts remain, steadily concentrating until reaching today's roughly 3.5% salinity.

Verify in Arize AX

  1. Open your Arize AX space and select project mcp-tracing-example.
  2. You should see a single new trace within ~30 seconds containing both client- and server-side spans: an Agent workflow root span (AGENT) wrapping Ocean Assistant, turn (CHAIN), mcp_tools (CHAIN), response (LLM) child spans from the client, and an explain_ocean (TOOL) span emitted by the server. The server’s tool span is parented under the client’s agent — that unified parenting is the value MCPInstrumentor provides.
  3. If no traces appear, see Troubleshooting.

Troubleshooting

  • No traces in Arize AX. Confirm ARIZE_SPACE_ID and ARIZE_API_KEY are set in the same shell that runs client.py. Enable OpenTelemetry debug logs with export OTEL_LOG_LEVEL=debug and re-run.
  • Connection closed from MCPServerStdio. Almost always something the server wrote to stdout before the MCP handshake completed. Confirm the server’s register() call uses verbose=False, and remove any print(...) statements from server module-level code.
  • KeyError: 'ARIZE_PROJECT_NAME' (or any other env var) in the server. The client is not passing its environment through to the subprocess. Make sure the params dict in MCPServerStdio(...) includes "env": dict(os.environ).
  • Client and server show up as separate traces. MCPInstrumentor().instrument(...) must run in both processes, before mcp / agents.mcp is imported. If only one side is instrumented, context isn’t propagated and each side gets its own trace.
  • 401 from OpenAI. Verify OPENAI_API_KEY is set and has access to gpt-5. The client and the server’s tool both call OpenAI; both need the key. The single export OPENAI_API_KEY=... from Configure credentials covers both because the client passes env=dict(os.environ) to the subprocess.
  • Different LLM provider on the server. Swap the server’s openai.OpenAI() + the model name for the provider you want, and add the corresponding OpenInference instrumentor to instrumentation_server.py (e.g. openinference-instrumentation-anthropic for Anthropic). The client’s instrumentation does not need to change.

Resources

Model Context Protocol Specification

OpenInference MCP Instrumentor

MCP Python SDK

End-to-End Example (Client + Server)