Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions content/en/llm_observability/guide/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 >}}
367 changes: 367 additions & 0 deletions content/en/llm_observability/monitoring/mcp_client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,367 @@
---
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=<YOUR_ML_APP_NAME>
export DD_LLMOBS_AGENTLESS_ENABLED=true
export DD_API_KEY=<YOUR_API_KEY>
export DD_SITE=<YOUR_DATADOG_SITE>
{{< /code-block >}}

3. Running your application with the `ddtrace-run` command:

{{< code-block lang="shell">}}
ddtrace-run <YOUR_APP_STARTUP_COMMAND>
{{< /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<Object> 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<Object> 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 >}}

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=<YOUR_ML_APP_NAME>
export DD_LLMOBS_AGENTLESS_ENABLED=true
export DD_API_KEY=<YOUR_API_KEY>
export DD_SITE=<YOUR_DATADOG_SITE>
{{< /code-block >}}

8. Run your application:

{{< tabs >}}

{{% tab "Python" %}}
{{< code-block lang="python">}}
ddtrace-run <YOUR_APP_STARTUP_COMMAND>
{{< /code-block >}}
{{% /tab %}}

{{% tab "Node.js" %}}
{{< code-block lang="javascript">}}
NODE_OPTIONS="--import dd-trace/initialize.mjs" <YOUR_APP_STARTUP_COMMAND>
{{< /code-block >}}
{{% /tab %}}

{{% tab "Java" %}}
{{< code-block lang="java">}}
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>
{{< /code-block >}}
{{% /tab %}}

{{< /tabs >}}
Loading