Skip to main content
Using LangChain, Langflow, LangGraph, or LangServe? See the dedicated LangChain integrationLangSightLangChainCallback covers all four without wrapping individual MCP clients.

Installation

LangSight is already installed — the SDK is part of the main package:
pip install langsight
# or
uv add langsight

2-line integration

from langsight.sdk import LangSightClient

client = LangSightClient(url="http://localhost:8000")           # line 1
traced_mcp = client.wrap(mcp_session, server_name="my-server") # line 2

# All call_tool() calls are now traced automatically
result = await traced_mcp.call_tool("query", {"sql": "SELECT 1"})

Full example

import asyncio
from mcp import ClientSession
from mcp.client.stdio import stdio_client, StdioServerParameters
from langsight.sdk import LangSightClient

async def main():
    # Start LangSight client
    langsight = LangSightClient(
        url="http://localhost:8000",
        api_key="your-api-key",      # optional
    )

    # Connect to your MCP server
    params = StdioServerParameters(command="python", args=["server.py"])
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # Wrap the session — one line
            traced = langsight.wrap(
                session,
                server_name="postgres-mcp",
                agent_name="support-agent",    # optional metadata
                session_id="sess-abc123",       # optional metadata
            )

            # Use exactly like the original session
            result = await traced.call_tool("query", {"sql": "SELECT COUNT(*) FROM orders"})
            print(result)

asyncio.run(main())

How it works

The wrap() method returns MCPClientProxy — a transparent proxy that:
  1. Calls the original call_tool() method
  2. Records start time, end time, and latency
  3. Sends a ToolCallSpan to POST /api/traces/spans asynchronously (fire-and-forget)
  4. Returns the original result unchanged
Fail-open: if LangSight is unreachable, the tool call still succeeds and the error is logged. Your agents are never blocked by monitoring.

Span metadata

traced = langsight.wrap(
    session,
    server_name="my-mcp",      # required — matches MCPServer.name in config
    agent_name="my-agent",     # shown in investigate output
    session_id="sess-123",     # groups spans into a conversation
    trace_id="trace-abc",      # links to a parent trace
)

Manual spans

Record spans without wrapping a client:
from datetime import UTC, datetime
from langsight.sdk.models import ToolCallSpan, ToolCallStatus

started = datetime.now(UTC)
# ... your tool call ...

await langsight.send_span(ToolCallSpan.record(
    server_name="my-server",
    tool_name="custom_tool",
    started_at=started,
    status=ToolCallStatus.SUCCESS,
))

Multi-agent tracing

When Agent A delegates to Agent B, pass the trace_id and the parent agent’s span ID so LangSight can reconstruct the full call tree.

Using the session context manager

from langsight.sdk import LangSightClient

client = LangSightClient(url="http://localhost:8000")

async def orchestrator_agent():
    # Opens a session — all wrap() calls within inherit trace_id + session_id
    async with client.agent_session(agent_name="orchestrator") as session:
        traced_jira = client.wrap(jira_session, server_name="jira-mcp")
        await traced_jira.call_tool("get_issue", {"id": "TASK-42"})

        # Pass session context to sub-agent
        await research_agent(parent_session=session)

async def research_agent(parent_session):
    # Sub-agent spans are linked to the parent session via parent_span_id
    async with client.agent_session(
        agent_name="research",
        parent_span_id=parent_session.current_span_id,
        trace_id=parent_session.trace_id,
        session_id=parent_session.session_id,
    ) as session:
        traced_confluence = client.wrap(
            confluence_session,
            server_name="confluence-mcp",
        )
        await traced_confluence.call_tool("search", {"query": "returns policy"})

Manual parent_span_id

If you are not using the session context manager, set parent_span_id explicitly:
traced = client.wrap(
    mcp_session,
    server_name="confluence-mcp",
    agent_name="research-agent",
    session_id="sess-f2a9b1",
    trace_id="trace-abc123",
    parent_span_id="span-001",   # ID of the handoff span from the parent agent
)

How parent_span_id works

parent_span_id uses the same model as OpenTelemetry distributed tracing. Each span has a unique span_id. When a child agent sets parent_span_id, LangSight can reconstruct the full call tree by following parent-child relationships from the flat span storage. No separate tree structure is required — tree reconstruction is a recursive query at read time.