---
title: "MCP Telemetry sample"
url: "https://nsta1.github.io/Orleans.Lattice/samples/McpTelemetry/README.html"
source: "https://github.com/NSTA1/Orleans.Lattice/blob/release/9.9/samples/McpTelemetry/README.md"
documents: "Orleans.Lattice 9.9.0 (release line 9.9)"
built: "2026-10-04"
all-pages: "https://nsta1.github.io/Orleans.Lattice/llms.txt"
---
# MCP Telemetry sample

Part of [Samples](../index.md).

A single-process demonstration of the optional
`Orleans.Lattice.Api.Mcp.Telemetry` add-on. It co-hosts a single-silo Orleans
cluster with the Model Context Protocol (MCP) server, exports the cluster's
`orleans.lattice` metrics over Prometheus, and drives the telemetry tools with a
real MCP client - proxying a **real Prometheus instance running in Docker**.

The round-trip is genuine: the Docker Prometheus scrapes this process's
`/metrics` endpoint, and this process's `lattice_telemetry_*` tools then query
that same Prometheus over its PromQL HTTP API.

```
  [ this process ]                         [ docker compose ]
  Orleans silo + Lattice                   prometheus:9090
    |  emits orleans.lattice metrics          ^   |
    |  /metrics  (OTel Prometheus exporter) --/   |  PromQL HTTP API
    |                                             v
  MCP server + AddTelemetryTools  -----------> queries Prometheus
    ^
    |  streamable HTTP + in-process MCP client
  AI agent journey
```

It proves the headline properties of the telemetry surface:

1. **Capability-gated discovery.** An agent granted the cluster-wide
   `LatticeOperation.Telemetry` capability discovers the four read-only
   `lattice_telemetry_*` tools and runs a live PromQL query end-to-end over MCP.
2. **Permission-scoping.** The same agent, granted *only* telemetry, does **not**
   see the state tools the server also registers - and an unauthenticated caller
   is offered nothing at all.
3. **The dual-credential boundary.** The tools authenticate to Prometheus with a
   backend credential the host configures (here `None`, because the sample's
   Prometheus is unauthenticated), never the caller's Lattice identity.

> **Known issue: the sample registers no `ILatticeApiMcpAuthorizer`.**
> `AddLatticeMcp` therefore falls back to the default `DenyAllMcpAuthorizer`,
> which the discovery core consults for every group tool - the telemetry tools
> included - when it builds the tool list and again when a tool is called;
> `RequireAuthorization = false` does not lift that gate. As written, the agent
> is offered only the `lattice_capabilities` meta-tool, so the four telemetry
> tools never appear and the run cannot complete property 1 or the live queries
> below: after waiting for the telemetry tools, its first `lattice_telemetry_query`
> call names a tool the session does not offer, which the MCP server answers with
> a protocol error, so the client throws and the run ends there, before the
> anonymous-caller act. The anonymous caller would still be offered nothing.
> Registering
> `AllowAllMcpAuthorizer` (or your own `ILatticeApiMcpAuthorizer`) is the
> missing step for the agent journey.

## Run it

The sample needs a Prometheus to talk to, so start it first with Docker, then run
the sample:

```
docker compose -f samples/McpTelemetry/docker-compose.yml up -d
dotnet run --project samples/McpTelemetry/McpTelemetry.csproj
```

The sample seeds an `agent` subject with a cluster-wide telemetry grant, drives a
burst of writes and reads to populate the `orleans.lattice` metrics, then is
written to (as written it stops short - see the known issue above):

- prints the four telemetry tools the agent discovered (and confirms it sees zero
  state tools),
- waits for Prometheus to scrape the silo, then runs `lattice_telemetry_query` for
  the silo's scrape-health (`up`),
- lists the Lattice metric names Prometheus discovered and queries one (in
  Prometheus form, `orleans_lattice_*`: the OpenTelemetry exporter turns the
  dots into underscores and appends unit words such as `_milliseconds` and
  `_bytes`, and `_total` to counters),
- shows the anonymous caller being offered zero tools,

and exits. Tear Prometheus down afterwards with:

```
docker compose -f samples/McpTelemetry/docker-compose.yml down
```

Prometheus scrapes the host process at `host.docker.internal:5290`; on Docker
Desktop this resolves automatically, and the compose file adds a host-gateway
mapping so it also works on Linux. If the sample reports that Prometheus did not
report the silo as up, confirm `docker compose up -d` is running and that port
`5290` is reachable from the container.

Authorization on the MCP endpoint is disabled purely to keep the sample
one-command runnable with no identity provider: a demo credential bridge maps a
request carrying a marker header onto a fixed `agent` credential. A real
deployment leaves `RequireAuthorization` at its secure default, registers an
`ILatticeApiMcpAuthorizer`, and lifts an authenticated ASP.NET Core principal
onto the ambient credential instead, and
points the telemetry proxy at an authenticated Prometheus with a `Bearer`,
`Basic`, or `MutualTls` backend credential, or a rotating `DynamicBearer` token.

## What to look at

- `Program.cs` - the silo + MCP host wiring (`AddOpenTelemetry().WithMetrics(...)`,
  `AddLatticeMcp` / `AddStateTools` / `AddTelemetryTools` / `MapLatticeMcp` /
  `MapPrometheusScrapingEndpoint`, with the MCP endpoint mounted at `/mcp` so it
  coexists with `/metrics`), the cluster-wide telemetry grant seeding, and the MCP
  client journey.
- `docker-compose.yml` and `prometheus.yml` - the real Prometheus that scrapes
  the silo and answers the telemetry tools' PromQL queries.
- `DemoCredentialBridge.cs` / `DemoAuthenticator.cs` - the fail-closed demo
  identity plumbing (shared shape with the `McpServer` sample).
- The package docs under
  [`docs/lattice.api.mcp.telemetry/`](../../docs/lattice.api.mcp.telemetry/README.md)
  cover the tool catalogue, the dual-credential trust boundary, and the
  metric-access allow-list in depth.
