---
title: MCP Clients
description: Learn how to instrument and monitor MCP clients with LLM Observability.
breadcrumbs: Docs > LLM Observability > Monitoring > MCP Clients
---

# MCP Clients

{% callout %}
# Important note for users on the following Datadog sites: app.ddog-gov.com

{% alert level="danger" %}
This product is not supported for your selected [Datadog site](https://docs.datadoghq.com/getting_started/site.md). ().
{% /alert %}

{% /callout %}

You can monitor your MCP clients with Datadog LLM Observability in two ways:

- Automatic instrumentation: If you are using the official [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
- Manual instrumentation: If you are not using the official MCP Python SDK, or your MCP clients are written in Node.js or Java

## Automatically instrument your MCP client{% #automatically-instrument-your-mcp-client %}

If you are using the official MCP Python SDK to connect to an MCP server with an MCP client session, use the following steps to automatically instrument your MCP client application:

1. Install `ddtrace`:

   ```shell
   pip install ddtrace
```

1. Set the required environment variables:

   ```shell
   export DD_LLMOBS_ENABLED=true
   export DD_LLMOBS_ML_APP=<YOUR_ML_APP_NAME>
   export DD_LLMOBS_AGENTLESS_ENABLED=true
   export DD_API_KEY=<YOUR_API_KEY>
   export DD_SITE=
   ```

1. Run your application with the `ddtrace-run` command:

   ```shell
   ddtrace-run <YOUR_APP_STARTUP_COMMAND>
```

## Manually instrument your MCP client{% #manually-instrument-your-mcp-client %}

You can also manually instrument your MCP client by using Datadog's [LLM Observability SDKs](https://docs.datadoghq.com/llm_observability/instrumentation/sdk.md) for Python, Node.js, and Java. Use the following steps to add the required tags and spans:

1. Import the LLM Observability SDK:

   {% tab title="Python" %}

   ```python
   from ddtrace.llmobs import LLMObs
```

   {% /tab %}

   {% tab title="Node.js" %}

   ```javascript
   const { llmobs } = require('dd-trace');
```

   {% /tab %}

   {% tab title="Java" %}

   ```java
   import datadog.trace.api.llmobs.LLMObs;
```

   {% /tab %}

1. Start a workflow span around your MCP client session:

   {% tab title="Python" %}

   ```python
   with LLMObs.workflow(name="MCP Client Session") as client_session_span:
       # MCP client session logic
```

   {% /tab %}

   {% tab title="Node.js" %}

   ```javascript
   llmobs.trace({ kind: 'workflow', name: 'MCP Client Session' }, async (clientSessionSpan) => {
     // MCP client session logic
   })
```

   {% /tab %}

   {% tab title="Java" %}

   ```java
   LLMObsSpan clientSessionSpan = LLMObs.startWorkflowSpan("MCP Client Session");
   // MCP client session logic
   clientSessionSpan.finish();
```

   {% /tab %}

1. Within your client session, start a task span around your MCP client session initialization call. Annotate the parent client session span with the server information returned from the initialization call.

   {% tab title="Python" %}

   ```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,
           }
       )
```

   {% /tab %}

   {% tab title="Node.js" %}

   ```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,
       }
     });
   });
```

   {% /tab %}

   {% tab title="Java" %}

   ```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();
```

   {% /tab %}

1. Start a task span around your call to the MCP server for getting the list of available tools within your client session.

   {% tab title="Python" %}

   ```python
   with LLMObs.task(name="MCP Client Session List Tool"):
       tools = list_tools()
       LLMObs.annotate(
         output_data=tools,
       )
```

   {% /tab %}

   {% tab title="Node.js" %}

   ```javascript
   const tools = await llmobs.trace({ kind: 'task', name: 'MCP Client Session List Tool' }, async () => {
     const tools = await listTools();
     llmobs.annotate({
       outputData: tools,
     });
   
     return tools;
   });
```

   {% /tab %}

   {% tab title="Java" %}

   ```java
   LLMObsSpan toolsListTaskSpan = LLMObs.startTaskSpan("MCP Client Session List Tool");
   List<Object> tools = listTools();
   toolsListTaskSpan.annotateIO(null, tools);
   toolsListTaskSpan.finish();
```

   {% /tab %}

1. Within your client session, start a tool span around your tool calls to the MCP server. Annotate the tool span with:

   - The server name from the information returned from the initialization call
   - The MCP tool type (`client`; for server-side monitoring, use `server`)

If the tool call returns an error from the MCP server (even if it would not normally raise or throw an error) mark the tool span with an error.

   {% tab title="Python" %}

   ```python
   name, arguments = get_next_tool_call()
   with LLMObs.tool(name=f"MCP Client Tool: {name}") as tool_span:
       result = call_tool(name, arguments)
   
       if result.isError:
           tool_span.error = 1
           tool_span.set_tag("error.message", result.content[0].text)
   
       LLMObs.annotate(
               input_data=arguments,
               output_data=result,
               tags={
                 "mcp_server_name": server_info.name,
                 "mcp_tool_kind": "client",
               }
           )
```

   {% /tab %}

   {% tab title="Node.js" %}

   ```javascript
   const { name, arguments } = await getNextToolCall();
   llmobs.trace({ kind: 'tool', name: `MCP Client Tool: ${name}` }, async () => {
     const result = await callTool(name, arguments);
   
     if (result.isError) {
       toolSpan.setTag("error", true);
       toolSpan.setTag("error.message", result.content[0].text);
     }
   
     llmobs.annotate({
       inputData: arguments,
       outputData: result,
       tags: { mcp_server_name: serverInfo.name, mcp_tool_kind: "client" }
     });
   })
```

   {% /tab %}

   {% tab title="Java" %}

   ```java
   Object tool = await getNextToolCall();
   LLMObsSpan toolSpan = LLMObs.startToolSpan("MCP Client Tool: " + tool.name());
   Object result = callTool(tool.name(), tool.arguments());
   
   if (result.isError) {
     toolSpan.setError(true);
     toolSpan.setErrorMessage(result.content[0].text);
   }
   
   toolSpan.annotateIO(arguments, result);
   toolSpan.setTag("mcp_tool_kind", "client");
   toolSpan.setTag("mcp_server_name", serverInfo.name());
   toolSpan.finish();
```

   {% /tab %}

1. At this point, your instrumentation is complete. Your code should resemble the following:

   {% tab title="Python" %}

   ```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 Tool"):
           tools = list_tools()
           LLMObs.annotate(
             output_data=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)
   
           if result.isError:
               tool_span.error = 1
               tool_span.set_tag("error.message", result.content[0].text)
   
           LLMObs.annotate(
               input_data=arguments,
               output_data=result,
               tags={
                 "mcp_server_name": server_info.name,
                 "mcp_tool_kind": "client",
               }
           )
```

   {% /tab %}

   {% tab title="Node.js" %}

   ```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,
         }
       });
     });
   
     const tools = await llmobs.trace({ kind: 'task', name: 'MCP Client Session List Tool' }, async () => {
       const tools = await listTools();
       llmobs.annotate({
         outputData: tools,
       });
   
       return tools;
     });
   
     // 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);
   
       if (result.isError) {
         toolSpan.setTag("error", true);
         toolSpan.setTag("error.message", result.content[0].text);
       }
   
       llmobs.annotate({
         inputData: arguments,
         outputData: result,
         tags: { mcp_server_name: serverInfo.name, mcp_tool_kind: "client" }
       });
     });
   })
```

   {% /tab %}

   {% tab title="Java" %}

   ```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 toolsListTaskSpan = LLMObs.startTaskSpan("MCP Client Session List Tool");
   List<Object> tools = listTools();
   toolsListTaskSpan.annotateIO(null, tools);
   toolsListTaskSpan.finish();
   
   // tool calls as part of a user feedback loop or user interaction
   Object tool = getNextToolCall();
   LLMObsSpan toolSpan = LLMObs.startToolSpan("MCP Client Tool: " + tool.name());
   Object result = callTool(tool.name(), tool.arguments());
   
   if (result.isError) {
     toolSpan.setTag("error", true);
     toolSpan.setTag("error.message", result.content[0].text);
   }
   
   toolSpan.annotateIO(tool.arguments(), result);
   toolSpan.setTag("mcp_tool_kind", "client");
   toolSpan.setTag("mcp_server_name", serverInfo.name());
   toolSpan.finish();
   // ... more tool calls, tasks, or user interactions
   clientSessionSpan.finish(); // finish at the end of the client session scope
```

   {% /tab %}

1. Set the required environment variables:

   ```shell
   export DD_LLMOBS_ENABLED=true
   export DD_LLMOBS_ML_APP=<YOUR_ML_APP_NAME>
   export DD_LLMOBS_AGENTLESS_ENABLED=true
   export DD_API_KEY=<YOUR_API_KEY>
   export DD_SITE=
   ```

1. Run your application:

   {% tab title="Python" %}

   ```shell
   ddtrace-run <YOUR_APP_STARTUP_COMMAND>
```

   {% /tab %}

   {% tab title="Node.js" %}

   ```shell
   NODE_OPTIONS="--import dd-trace/initialize.mjs" <YOUR_APP_STARTUP_COMMAND>
```

   {% /tab %}

   {% tab title="Java" %}

   ```shell
   java -javaagent:dd-java-agent.jar -Ddd.llmobs.enabled=true -Ddd.llmobs.ml-app=<YOUR_ML_APP_NAME> -Ddd.llmobs.agentless-enabled=true -Ddd.api-key=<YOUR_API_KEY> -Ddd.site=<YOUR_DATADOG_SITE> <YOUR_APP_STARTUP_COMMAND>
```

   {% /tab %}

## Further Reading{% #further-reading %}

- [Gain end-to-end visibility into MCP clients with Datadog LLM Observability](https://www.datadoghq.com/blog/mcp-client-monitoring)
