From 11232a39b70c80117aa83fd37477b5199956eb1a Mon Sep 17 00:00:00 2001 From: Sam Brenner Date: Thu, 30 Oct 2025 09:44:22 -0400 Subject: [PATCH 1/2] add guide for mcp client monitoring --- content/en/llm_observability/guide/_index.md | 1 + .../monitoring/mcp_client.md | 333 ++++++++++++++++++ 2 files changed, 334 insertions(+) create mode 100644 content/en/llm_observability/monitoring/mcp_client.md diff --git a/content/en/llm_observability/guide/_index.md b/content/en/llm_observability/guide/_index.md index 68cf5c315c98b..6c68678d6538f 100644 --- a/content/en/llm_observability/guide/_index.md +++ b/content/en/llm_observability/guide/_index.md @@ -15,4 +15,5 @@ cascade: {{< nextlink href="/llm_observability/trace_proxy_services" >}}Trace Proxy and Gateway Services{{< /nextlink >}} {{< nextlink href="/llm_observability/evaluations/" >}}Evaluations{{< /nextlink >}} {{< nextlink href="/llm_observability/guide/llm_observability_and_apm" >}}Using LLM Observability and APM{{< /nextlink >}} + {{< nextlink href="/llm_observability/guide/mcp_client" >}}Monitor MCP Clients{{< /nextlink >}} {{< /whatsnext >}} diff --git a/content/en/llm_observability/monitoring/mcp_client.md b/content/en/llm_observability/monitoring/mcp_client.md new file mode 100644 index 0000000000000..c38add092a926 --- /dev/null +++ b/content/en/llm_observability/monitoring/mcp_client.md @@ -0,0 +1,333 @@ +--- +title: Monitor MCP Clients +description: Learn how to instrument and monitor MCP clients with LLM Observability. +aliases: + - /llm_observability/guide/mcp_client +--- + +## Automatic Instrumentation + +If you are using the official MCP Python package to connect to an MCP server with an MCP client session, you can enable automatic instrumentation by + +1. Installing the `ddtrace` package: + +{{< code-block lang="shell">}} +pip install ddtrace +{{< /code-block >}} + +2. Setting the required environment variables: + +{{< code-block lang="shell">}} +export DD_LLMOBS_ENABLED=true +export DD_LLMOBS_ML_APP= +export DD_LLMOBS_AGENTLESS_ENABLED=true +export DD_API_KEY= +export DD_SITE= +{{< /code-block >}} + +3. Running your application with the `ddtrace-run` command: + +{{< code-block lang="shell">}} +ddtrace-run +{{< /code-block >}} + +## Manual Instrumentation + +If you are not using the official MCP Python package, or your MCP clients are written in a different language, you can manually instrument your MCP clients using the LLM Observability SDKs to add the required spans and tags to utilize the LLM Observability MCP client monitoring features. + +1. Import the LLM Observability SDK: + +{{< tabs >}} + +{{% tab "Python" %}} +{{< code-block lang="python">}} +from ddtrace.llmobs import LLMObs +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Node.js" %}} +{{< code-block lang="javascript">}} +const { llmobs } = require('dd-trace'); +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Java" %}} +{{< code-block lang="java">}} +import datadog.trace.api.llmobs.LLMObs; +{{< /code-block >}} +{{% /tab %}} + +{{< /tabs >}} + +2. Start a workflow span around your MCP client session: + +{{< tabs >}} + +{{% tab "Python" %}} +{{< code-block lang="python">}} +with LLMObs.workflow(name="MCP Client Session") as client_session_span: + # MCP client session logic +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Node.js" %}} +{{< code-block lang="javascript">}} +llmobs.trace({ kind: 'workflow', name: 'MCP Client Session' }, async (clientSessionSpan) => { + // MCP client session logic +}) +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Java" %}} +{{< code-block lang="java">}} +LLMObsSpan clientSessionSpan = LLMObs.startWorkflowSpan("MCP Client Session"); +// MCP client session logic +clientSessionSpan.finish(); +{{< /code-block >}} +{{% /tab %}} + +{{< /tabs >}} + +3. Start a task span around your MCP client session initialization call within your client session. Annotate the parent client session span with the server information returned from the initialization call. + +{{< tabs >}} + +{{% tab "Python" %}} +{{< code-block lang="python">}} +with LLMObs.task(name="MCP Client Session Initialization"): + server_info = initialize_mcp_client() + LLMObs.annotate( + client_session_span, + tags={ + "mcp_server_name": server_info.name, + "mcp_server_version": server_info.version, + "mcp_server_title": server_info.title, + } + ) +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Node.js" %}} +{{< code-block lang="javascript">}} +llmobs.trace({ kind: 'task', name: 'MCP Client Session Initialization' }, async () => { + const serverInfo = await initializeMcpClient(); + llmobs.annotate(clientSessionSpan, { + tags: { + mcp_server_name: serverInfo.name, + mcp_server_version: serverInfo.version, + mcp_server_title: serverInfo.title, + } + }); +}); +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Java" %}} +{{< code-block lang="java">}} +LLMObsSpan initializationTaskSpan = LLMObs.startTaskSpan("MCP Client Session Initialization"); +Object serverInfo = initializeMcpClient(); +clientSessionSpan.setTags(Map.of( + "mcp_server_name", serverInfo.name(), + "mcp_server_version", serverInfo.version(), + "mcp_server_title", serverInfo.title(), +)); +initializationTaskSpan.finish(); +{{< /code-block >}} +{{% /tab %}} + +{{< /tabs >}} + +4. Start a task span around your call to the MCP server for getting the list of available tools within your client session. Annotate the parent client session span with the number of tools returned from the MCP server the client connected to. + +{{< tabs >}} + +{{% tab "Python" %}} +{{< code-block lang="python">}} +with LLMObs.task(name="MCP Client Session List Tools"): + tools = list_tools() + LLMObs.annotate( + client_session_span, + tags={ + "mcp_num_tools": len(tools), + } + ) +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Node.js" %}} +{{< code-block lang="javascript">}} +llmobs.trace({ kind: 'task', name: 'MCP Client Session List Tools' }, async () => { + const tools = await listTools(); + llmobs.annotate(clientSessionSpan, { + tags: { + mcp_num_tools: tools.length, + } + }); +}); +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Java" %}} +{{< code-block lang="java">}} +LLMObsSpan listToolsSpan = LLMObs.startTaskSpan("MCP Client Session List Tools"); +List tools = listTools(); +clientSessionSpan.setTags(Map.of( + "mcp_num_tools", tools.length, +)); +listToolsSpan.finish(); +{{< /code-block >}} +{{% /tab %}} + +{{< /tabs >}} + +5. Start a tool span around your tool calls to the MCP server within your client session. Annotate the tool span with the MCP tool type ("client", as opposed to "server" for server-side monitoring). + +{{< tabs >}} + +{{% tab "Python" %}} +{{< code-block lang="python">}} +name, arguments = get_next_tool_call() +with LLMObs.tool(name=f"MCP Client Tool: {name}") as tool_span: + result = call_tool(name, arguments) + LLMObs.annotate( + input_data=arguments, + output_data=result, + tags={ + "mcp_tool_kind": "client", + } + ) +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Node.js" %}} +{{< code-block lang="javascript">}} +const { name, arguments } = await getNextToolCall(); +llmobs.trace({ kind: 'tool', name: `MCP Client Tool: ${name}` }, async () => { + const result = await callTool(name, arguments); + llmobs.annotate({ + inputData: arguments, + outputData: result, + tags: { mcp_tool_kind: "client" } + }); +}) +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Java" %}} +{{< code-block lang="java">}} +Object tool = await getNextToolCall(); +LLMObsSpan toolSpan = LLMObs.startToolSpan("MCP Client Tool: " + tool.name()); +Object result = callTool(tool.name(), tool.arguments()); +toolSpan.annotateIO(arguments, result); +toolSpan.setTag("mcp_tool_kind", "client"); +toolSpan.finish(); +{{< /code-block >}} +{{% /tab %}} + +{{< /tabs >}} + +6. In total, your MCP client should be instrumented as follows + +{{< tabs >}} + +{{% tab "Python" %}} +{{< code-block lang="python">}} +with LLMObs.workflow(name="MCP Client Session") as client_session_span: + with LLMObs.task(name="MCP Client Session Initialization"): + server_info = initialize_mcp_client() + LLMObs.annotate( + client_session_span, + tags={ + "mcp_server_name": server_info.name, + "mcp_server_version": server_info.version, + "mcp_server_title": server_info.title, + } + ) + with LLMObs.task(name="MCP Client Session List Tools"): + tools = list_tools() + LLMObs.annotate( + client_session_span, + tags={ + "mcp_num_tools": len(tools), + } + ) + + # tool calls as part of a user feedback loop or user interaction + name, arguments = get_next_tool_call() + with LLMObs.tool(name=f"MCP Client Tool: {name}") as tool_span: + result = call_tool(name, arguments) + LLMObs.annotate( + input_data=arguments, + output_data=result, + tags={ + "mcp_tool_kind": "client", + } + ) +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Node.js" %}} +{{< code-block lang="javascript">}} +llmobs.trace({ kind: 'workflow', name: 'MCP Client Session' }, async (clientSessionSpan) => { + llmobs.trace({ kind: 'task', name: 'MCP Client Session Initialization' }, async () => { + const serverInfo = await initializeMcpClient(); + llmobs.annotate(clientSessionSpan, { + tags: { + mcp_server_name: serverInfo.name, + mcp_server_version: serverInfo.version, + mcp_server_title: serverInfo.title, + } + }); + }); + llmobs.trace({ kind: 'task', name: 'MCP Client Session List Tools' }, async () => { + const tools = await listTools(); + llmobs.annotate(clientSessionSpan, { + tags: { + mcp_num_tools: tools.length, + } + }); + }); + // tool calls as part of a user feedback loop or user interaction + const { name, arguments } = await getNextToolCall(); + llmobs.trace({ kind: 'tool', name: `MCP Client Tool: ${name}` }, async () => { + const result = await callTool(name, arguments); + llmobs.annotate({ + inputData: arguments, + outputData: result, + tags: { mcp_tool_kind: "client" } + }); + }); +}) +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Java" %}} +{{< code-block lang="java">}} +LLMObsSpan clientSessionSpan = LLMObs.startWorkflowSpan("MCP Client Session"); +LLMObsSpan initializationTaskSpan = LLMObs.startTaskSpan("MCP Client Session Initialization"); +Object serverInfo = initializeMcpClient(); +clientSessionSpan.setTags(Map.of( + "mcp_server_name", serverInfo.name(), + "mcp_server_version", serverInfo.version(), + "mcp_server_title", serverInfo.title(), +)); +initializationTaskSpan.finish(); +LLMObsSpan listToolsSpan = LLMObs.startTaskSpan("MCP Client Session List Tools"); +List tools = listTools(); +clientSessionSpan.setTags(Map.of( + "mcp_num_tools", tools.length, +)); +listToolsSpan.finish(); +// tool calls as part of a user feedback loop or user interaction +Object tool = await getNextToolCall(); +LLMObsSpan toolSpan = LLMObs.startToolSpan("MCP Client Tool: " + tool.name()); +Object result = callTool(tool.name(), tool.arguments()); +toolSpan.annotateIO(tool.arguments(), result); +toolSpan.setTag("mcp_tool_kind", "client"); +toolSpan.finish(); +// ... more tool calls, tasks, or user interactions +clientSessionSpan.finish(); // finish at the end of the client session scope +{{< /code-block >}} +{{% /tab %}} + +{{< /tabs >}} From 1a5ce33b2fd0398d634cc428b56967838aa6e846 Mon Sep 17 00:00:00 2001 From: Sam Brenner Date: Thu, 30 Oct 2025 13:08:13 -0400 Subject: [PATCH 2/2] add run instructions --- .../monitoring/mcp_client.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/content/en/llm_observability/monitoring/mcp_client.md b/content/en/llm_observability/monitoring/mcp_client.md index c38add092a926..549c2e28ffc9b 100644 --- a/content/en/llm_observability/monitoring/mcp_client.md +++ b/content/en/llm_observability/monitoring/mcp_client.md @@ -331,3 +331,37 @@ clientSessionSpan.finish(); // finish at the end of the client session scope {{% /tab %}} {{< /tabs >}} + +7. Set the necessary environment variables to enable MCP client monitoring: + +{{< code-block lang="shell">}} +export DD_LLMOBS_ENABLED=true +export DD_LLMOBS_ML_APP= +export DD_LLMOBS_AGENTLESS_ENABLED=true +export DD_API_KEY= +export DD_SITE= +{{< /code-block >}} + +8. Run your application: + +{{< tabs >}} + +{{% tab "Python" %}} +{{< code-block lang="python">}} +ddtrace-run +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Node.js" %}} +{{< code-block lang="javascript">}} +NODE_OPTIONS="--import dd-trace/initialize.mjs" +{{< /code-block >}} +{{% /tab %}} + +{{% tab "Java" %}} +{{< code-block lang="java">}} +java -javaagent:dd-java-agent.jar -Ddd.llmobs.enabled=true -Ddd.llmobs.ml-app= -Ddd.llmobs.agentless-enabled=true -Ddd.api-key= -Ddd.site= +{{< /code-block >}} +{{% /tab %}} + +{{< /tabs >}}