The Dapr MCP server is in alpha. Tool names, inputs, and configuration may change in a future release.
The Dapr MCP server (dapr/dapr-mcp-server) is a standalone Model Context Protocol (MCP) server, written in Go, that runs next to a Dapr sidecar. It turns the sidecar’s building blocks (state, pub/sub, bindings, secrets, service invocation, actors, distributed lock, cryptography, and conversation) into MCP tools that any MCP client can call: Claude Desktop, Cursor, Claude Code, VS Code, Dapr Agents, or your own agent.
How it differs from the rest of this section
The other pages in this section cover how the Dapr runtime connects agents to MCP servers you already have, either through service invocation or with the MCPServer resource. The Dapr MCP server works the other way round: it is an MCP server, and the tools it serves are Dapr APIs. It is a separate binary with its own release cycle, not a feature of daprd. The two fit together. You can run the Dapr MCP server as a Dapr app and reach it through the service invocation path, or declare it as an MCPServer resource like any other MCP server.
When to use it
Use the Dapr MCP server when you want an agent to:
Persist or read data through a Dapr state store, with transactions where the store supports them.
Publish events, call other Dapr apps or actors, or reach external systems through output bindings.
Coordinate with other workers using a distributed lock.
Read secrets, encrypt or decrypt data, or hand a prompt to another LLM through a conversation component.
The agent gets the same component abstraction your services use, so swapping Redis for PostgreSQL or Kafka for RabbitMQ needs no change to the agent.
You don’t need it for plain computation, one-off text generation, or local file and process access. Those don’t need a Dapr sidecar.
How it works
The server starts and connects to the Dapr sidecar over gRPC, using the standard Dapr SDK settings (DAPR_GRPC_PORT, DAPR_GRPC_ENDPOINT). It retries the connection up to five times, then exits.
It reads the sidecar’s metadata and registers tools for each building block that has at least one component loaded. Three tools are always registered: get_components, invoke_service, and invoke_actor_method.
It serves MCP over stdio (the default, for local clients that launch the server themselves) or over streamable HTTP (with --http, for remote and shared deployments). Authentication, health endpoints, and CORS apply to the HTTP transport only.
Tools follow the components loaded in the sidecar, with no restart. When component hot reloading adds the first component of a type (for example the first pub/sub component), the server registers that type’s tools the next time a client lists tools or calls get_components, and notifies connected clients that the tool list changed. Removing the last component of a type removes its tools. Clients that read the tool list only once, as many agent frameworks do, need to reconnect to see the change.
Every tool carries MCP safety annotations (readOnlyHint, destructiveHint, idempotentHint) and a description that tells the model when to use it. The server’s instructions tell the model to call get_components first and never to invent component names, keys, or topics.
Security considerations
The server can do anything its Dapr sidecar can do, and it returns tool results, including secret values from get_secret and get_bulk_secrets, to the model. Limit what the sidecar can reach:
Turn on authentication whenever you serve over HTTP to anything other than localhost.
Treat tools marked destructive (delete_state, execute_transaction, invoke_service, invoke_actor_method, invoke_output_binding, encrypt_data) as needing human confirmation in your MCP client.
Next steps
Getting started: install, run with a sidecar, and connect a client
An MCP client, such as Claude Desktop, Cursor, or Claude Code
Step 1: Install the server
Pick one of the following.
Each GitHub release publishes a binary per platform, named dapr-mcp-server-<os>-<arch>, with a checksums.txt file. Builds are available for linux-amd64, linux-arm64, darwin-amd64, darwin-arm64, and windows-amd64 (with an .exe suffix).
Download the binary, check it against checksums.txt, and move it onto your PATH. On Arm, use linux-arm64.
In PowerShell, download the binary to $Env:LOCALAPPDATA\dapr-mcp-server, check it against checksums.txt, and add the folder to your user PATH:
$base="https://github.com/dapr/dapr-mcp-server/releases/latest/download"$dir="$Env:LOCALAPPDATA\dapr-mcp-server"New-Item-ItemTypeDirectory-Force-Path$dir|Out-NullInvoke-WebRequest"$base/dapr-mcp-server-windows-amd64.exe"-OutFile"$dir\dapr-mcp-server.exe"Invoke-WebRequest"$base/checksums.txt"-OutFile"$dir\checksums.txt"$expected=((Select-String-Path"$dir\checksums.txt"-Pattern"dapr-mcp-server-windows-amd64.exe").Line-split"\s+")[0]$actual=(Get-FileHash"$dir\dapr-mcp-server.exe"-AlgorithmSHA256).Hash.ToLower()if($actual-ne$expected){throw"Checksum mismatch for dapr-mcp-server.exe"}$userPath=[Environment]::GetEnvironmentVariable("Path","User")if(($userPath-split";")-notcontains$dir){[Environment]::SetEnvironmentVariable("Path",(($userPath,$dir)|Where-Object{$_})-join";","User")}
Open a new terminal so the updated PATH takes effect.
</div>
Requires Go 1.26.6 or later.
go install github.com/dapr/dapr-mcp-server/cmd/dapr-mcp-server@latest
A binary built this way reports its version as dev, because the version is stamped in only by the release build.
Multi-arch images (linux/amd64, linux/arm64) are published to the GitHub Container Registry, tagged with the release version. Stable releases also update latest.
docker pull ghcr.io/dapr/dapr-mcp-server:latest
The image runs as a non-root user, listens on port 8080 with --http 0.0.0.0:8080 by default, and includes a HEALTHCHECK. See Run on Kubernetes for how to pair it with a sidecar.
Check the install:
dapr-mcp-server --version
Step 2: Define components
The server registers tools only for building blocks that have a component loaded. Create a components folder with an in-memory state store and pub/sub broker:
With these two components the server registers the three core tools plus the state and pub/sub tools. Add components of other types (bindings.*, secretstores.*, lock.*, crypto.*, conversation.*) to expose their tools. See the tool reference for the full mapping.
Step 3: Run and connect
Over stdio (Claude Desktop, Cursor)
With stdio, the MCP client starts the server process itself. Run the sidecar on its own first, on a fixed gRPC port:
dapr run --app-id dapr-mcp-server --dapr-grpc-port 50001 --resources-path ./components
Leave it running, then point your client at the binary. Use the absolute path, because clients don’t always inherit your shell’s PATH. On Windows, that’s the full path to dapr-mcp-server.exe, with each backslash doubled in JSON (for example, C:\\Users\\<you>\\AppData\\Local\\dapr-mcp-server\\dapr-mcp-server.exe).
Edit claude_desktop_config.json (on macOS, ~/Library/Application Support/Claude/claude_desktop_config.json; on Windows, %APPDATA%\Claude\claude_desktop_config.json) and restart Claude Desktop:
claude mcp add dapr --env DAPR_GRPC_PORT=50001 -- /usr/local/bin/dapr-mcp-server
Ask the assistant to list your Dapr components. It calls get_components and reports statestore and pubsub. From there, try “save the key greeting with value hello to the state store” and “read it back”.
The server writes its logs as JSON to stderr, so they never mix with the MCP messages on stdout. Most clients show stderr in their MCP log view.
Over streamable HTTP
Pass --http with a listen address to serve MCP over streamable HTTP. Here dapr run starts both the sidecar and the server, and sets DAPR_GRPC_PORT for it:
dapr run --app-id dapr-mcp-server --resources-path ./components -- dapr-mcp-server --http localhost:8080
The MCP endpoint is http://localhost:8080/. Check that the server is up and can reach its sidecar:
curl http://localhost:8080/readyz
Invoke-RestMethodhttp://localhost:8080/readyz
A 200 response with "status":"healthy" means it’s ready. A 503 means the sidecar isn’t reachable; PowerShell reports it as an error.
Without AUTH_MODE, anyone who can reach the HTTP port can call every tool. Listen on localhost for local use, and turn on authentication before exposing the server on a network.
Run on Kubernetes
On Kubernetes, run the container image with the Dapr sidecar injected. The injector sets DAPR_GRPC_PORT in the server’s container, so no extra configuration is needed to find the sidecar. Point the probes at the server’s health endpoints:
The Service makes the server reachable from any pod in the cluster, and without AUTH_MODE any caller can use every tool. Turn on authentication, or limit which pods can reach the server with a Kubernetes NetworkPolicy.
Add authentication and telemetry settings as env entries on the container; see the configuration reference.
Streamable HTTP sessions are held in the server’s memory. If you run more than one replica, configure session affinity on whatever routes traffic to them, so that each client keeps reaching the same replica.
Send traces, metrics, and logs to your backend with observability.
2 - Dapr MCP server configuration reference
Every command-line flag and environment variable the Dapr MCP server reads, with defaults
The Dapr MCP server takes a few command-line flags and reads everything else from environment variables. The server checks authentication settings at startup and exits with an error if they are invalid or incomplete, rather than starting without protection.
Command-line flags
Flag
Default
Description
--http <addr>
Not set (stdio)
Serve streamable HTTP on this address, for example localhost:8080 or 0.0.0.0:8080, instead of stdio. The health endpoints are served on the same address.
--version
false
Print the version and exit.
--health-check
false
Request /livez from a running server and exit with 0 on a 200 response, 1 otherwise. The container image uses this for its HEALTHCHECK.
--health-check-addr <host:port>
From --http, else localhost:8080
Address that --health-check probes. A wildcard host such as 0.0.0.0 is probed as localhost.
Server and Dapr connection
Variable
Default
Description
DAPR_GRPC_PORT
50001
Port of the Dapr sidecar’s gRPC API on 127.0.0.1. dapr run and the Kubernetes sidecar injector set this for you.
DAPR_GRPC_ENDPOINT
Not set
Full address of the sidecar’s gRPC API. Takes precedence over DAPR_GRPC_PORT.
Timeout for each attempt to connect to the sidecar. The server makes up to five attempts, two seconds apart, then exits.
DAPR_MCP_SERVER_LOG_LEVEL
info
Log level: debug, info, warn, or error. Logs are JSON on stderr. At debug, request headers are logged with credential headers redacted.
The DAPR_* connection variables are read by the Dapr Go SDK that the server uses.
HTTP transport
These settings apply only when the server runs with --http.
Variable
Default
Description
DAPR_MCP_CORS_ORIGIN
Not set
Origin allowed to call the server from a browser, for example https://app.example.com. When set, the server adds CORS headers to every response and answers OPTIONS preflight requests. When not set, no CORS headers are sent and browsers on other origins are refused. Set it only for a browser-based MCP client on another origin.
The server always serves /livez, /readyz, and /startupz without authentication; see Health endpoints. MCP requests are served on every other path, so http://<host>:<port>/ is the MCP endpoint. The one exception is /dapr/subscribe, which returns an empty subscription list for the Dapr sidecar and also needs no authentication.
Authentication
Authentication applies to the HTTP transport. See Authentication for how the modes work and for examples.
Variable
Default
Description
AUTH_MODE
Not set (disabled)
oidc, spiffe, dapr-sentry, or hybrid turns authentication on. Not set, or disabled, turns it off.
OIDC_ENABLED
false
In hybrid mode, accept OIDC tokens. Ignored in other modes.
OIDC_ISSUER_URL
Not set
OIDC issuer URL. Required when OIDC is on.
OIDC_CLIENT_ID
Not set
Expected aud claim. Required when OIDC is on.
OIDC_ALLOWED_ALGORITHMS
RS256,ES256
Comma-separated signing algorithms to accept.
OIDC_SKIP_ISSUER_CHECK
false
Skip the iss check. For development only.
SPIFFE_ENABLED
false
In hybrid mode, accept SPIFFE JWT-SVIDs. Ignored in other modes.
SPIFFE_TRUST_DOMAIN
Not set
Trust domain that callers must belong to. Required when SPIFFE is on.
SPIFFE_SERVER_ID
Not set
This server’s SPIFFE ID, which callers’ JWT-SVIDs must use as their audience. Required when SPIFFE is on.
SPIFFE_ENDPOINT_SOCKET
Not set
Address of the SPIFFE Workload API, for example unix:///run/spire/sockets/agent.sock.
SPIFFE_ALLOWED_CLIENTS
Not set (any workload in the trust domain)
Comma-separated SPIFFE IDs allowed to call the server.
DAPR_SENTRY_ENABLED
false
In hybrid mode, accept Dapr Sentry JWTs. Ignored in other modes.
DAPR_SENTRY_JWKS_URL
Not set
URL of the Sentry JWKS. Required when Dapr Sentry is on.
DAPR_SENTRY_TRUST_DOMAIN
Not set
Trust domain of the SPIFFE ID in the token’s sub claim. Required when Dapr Sentry is on.
DAPR_SENTRY_AUDIENCE
Not set
Expected aud claim. Required when Dapr Sentry is on.
DAPR_SENTRY_ISSUER
Not set
Expected iss claim. Checked only when set.
DAPR_SENTRY_TOKEN_HEADER
Authorization
Header to read the token from. Despite its name, it applies in every auth mode, not only Dapr Sentry. A custom header carries the raw token, without a Bearer prefix.
DAPR_SENTRY_JWKS_REFRESH_INTERVAL
5m
How often to refresh the JWKS, as a Go duration. Minimum 30s.
Telemetry
Telemetry is off until an OTLP endpoint is set. See Observability for what the server emits.
Variable
Default
Description
OTEL_EXPORTER_OTLP_ENDPOINT
Not set
OTLP collector endpoint for all signals, for example http://otel-collector:4317. An endpoint without a scheme uses TLS.
OTEL_EXPORTER_OTLP_PROTOCOL
grpc
grpc or http/protobuf. http/json falls back to http/protobuf.
OTEL_EXPORTER_OTLP_INSECURE
false
Use plaintext for an endpoint given without a scheme. An explicit http:// or https:// scheme always wins.
OTEL_EXPORTER_OTLP_HEADERS
Not set
Comma-separated key=value headers sent with every export, such as an API key. Values are percent-decoded.
OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_ENDPOINT
Not set
Per-signal endpoint, overriding the generic one. For HTTP, used as given unless it has no path, in which case /v1/traces, /v1/metrics, or /v1/logs is added.
OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_PROTOCOL
Generic value
Per-signal protocol.
OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_INSECURE
Generic value
Per-signal plaintext setting.
OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_HEADERS
Not set
Per-signal headers, merged over the generic ones.
OTEL_SERVICE_NAME
dapr-mcp-server
service.name resource attribute.
OTEL_SERVICE_VERSION
Binary version
service.version resource attribute.
OTEL_METRIC_EXPORT_INTERVAL
10000
Metric export interval in milliseconds. Go durations such as 30s are also accepted.
OTEL_LOG_EXPORT_INTERVAL
5000
Log export interval in milliseconds. Go durations are also accepted.
DAPR_MCP_SERVER_METRICS_ENABLED
true
Set to false to stop exporting metrics while keeping traces.
DAPR_MCP_SERVER_LOGS_OTEL_ENABLED
true
Set to false to stop exporting logs over OTLP. Logs are still written to stderr.
3 - Authenticating clients of the Dapr MCP server
Require OIDC, SPIFFE, or Dapr Sentry tokens on the Dapr MCP server’s HTTP transport
The Dapr MCP server can require a valid token on every MCP request it serves over streamable HTTP. Authentication is off by default. Set AUTH_MODE to turn it on:
AUTH_MODE
Accepts
Not set, or disabled
Any request. Use only on localhost.
oidc
ID or access tokens (JWTs) from an OpenID Connect provider
spiffe
SPIFFE JWT-SVIDs, validated with trust bundles from the SPIFFE Workload API
dapr-sentry
JWTs issued by Dapr Sentry for a Dapr app
hybrid
Any combination of the three above
Authentication only applies to the HTTP transport. Over stdio, the MCP client starts the server as a local process, and there is no network listener to protect.
The server checks the settings for the selected mode at startup. If a required variable is missing or a value can’t be parsed, it exits with an error that names the variable. An unrecognized AUTH_MODE value is also rejected at startup.
Sending a token
Clients send the token in the Authorization header, either as Bearer <token> or as the raw token. If DAPR_SENTRY_TOKEN_HEADER names another header, the server checks that header first, in every mode, and expects the raw token there.
A request with no token, or with a token that no enabled method accepts, gets 401 Unauthorized. The server doesn’t say which check failed. Set DAPR_MCP_SERVER_LOG_LEVEL=debug to log the reason on the server.
OIDC
Use oidc for tokens issued to users or applications by an identity provider such as Microsoft Entra ID, Okta, Auth0, Keycloak, or Google.
Variable
Required
Description
OIDC_ISSUER_URL
Yes
Issuer URL. The server fetches the provider’s discovery document from it at startup, and exits if it can’t.
OIDC_CLIENT_ID
Yes
Value the token’s aud claim must contain.
OIDC_ALLOWED_ALGORITHMS
No
Signing algorithms to accept. Default RS256,ES256.
OIDC_SKIP_ISSUER_CHECK
No
Skip the iss check. For development only; the server logs a warning when it’s on.
Use spiffe for service-to-service calls between workloads that get JWT-SVIDs from a SPIFFE implementation such as SPIRE. The server connects to the Workload API at startup to get trust bundles, waiting up to 10 seconds.
Variable
Required
Description
SPIFFE_TRUST_DOMAIN
Yes
Trust domain, for example example.org. Tokens from other trust domains are rejected.
SPIFFE_SERVER_ID
Yes
This server’s SPIFFE ID, for example spiffe://example.org/dapr-mcp-server. Callers must request their JWT-SVID with this ID as the audience.
SPIFFE_ENDPOINT_SOCKET
No
Workload API address, for example unix:///run/spire/sockets/agent.sock.
SPIFFE_ALLOWED_CLIENTS
No
Comma-separated SPIFFE IDs allowed to call the server. Each must be in the trust domain. When not set, any workload in the trust domain is accepted.
Use dapr-sentry when the callers are Dapr apps. Dapr Sentry can issue each app a JWT whose subject is the app’s SPIFFE ID, and publish the signing keys as a JWKS. To turn this on in Sentry, enable JWT issuing and the OIDC server, using the Sentry Helm values shown in Authenticating with a Federated Identity Credential. The Microsoft Entra ID steps on that page don’t apply. The JWKS is served at /jwks.json on the Sentry OIDC port. Each calling app requests a token for the audience you set in DAPR_SENTRY_AUDIENCE with the dapr.io/sentry-request-jwt-audiences annotation, or the --sentry-request-jwt-audiences flag in self-hosted mode.
An MCPServer resource with auth.spiffe.jwt sends such a token on every call, so this mode pairs naturally with declaring the Dapr MCP server as an MCPServer.
Variable
Required
Description
DAPR_SENTRY_JWKS_URL
Yes
URL of the Sentry JWKS.
DAPR_SENTRY_TRUST_DOMAIN
Yes
Trust domain of the caller’s SPIFFE ID, for example public. The sub claim must be a SPIFFE ID in this trust domain with a workload path.
DAPR_SENTRY_AUDIENCE
Yes
Value the token’s aud claim must contain. Use the audience your callers request their tokens for.
DAPR_SENTRY_ISSUER
No
Expected iss claim. When not set, the issuer isn’t checked.
DAPR_SENTRY_TOKEN_HEADER
No
Header that carries the raw token. Default Authorization. Applies in every mode; see Sending a token.
DAPR_SENTRY_JWKS_REFRESH_INTERVAL
No
How often to refresh the JWKS. Default 5m, minimum 30s.
The server fetches the JWKS at startup and exits if it can’t. Afterwards it refreshes the keys on the configured interval, and also when a token arrives signed with a key it doesn’t have, at most once every 30 seconds. If Sentry can’t be reached, the server keeps using the cached keys for up to 24 hours. Only asymmetric signing algorithms are accepted, the token must have an exp claim, and exp, nbf, and iat are checked with one minute of clock-skew leeway.
Hybrid
hybrid accepts tokens from more than one method. Turn on each method you want with its *_ENABLED variable, and set that method’s required variables. At least one method must be on.
The server tries the enabled methods in the order OIDC, SPIFFE, Dapr Sentry, and accepts the request as soon as one of them validates the token. The *_ENABLED variables are ignored in single-method modes.
Unauthenticated paths
The health endpoints /livez, /readyz, and /startupz are always served without authentication, as is /dapr/subscribe, which only tells the sidecar the server has no subscriptions. Every other path is MCP and needs a token. There is no setting to exempt more paths.
Every tool the Dapr MCP server exposes, its inputs, its safety properties, and the component type that enables it
The Dapr MCP server exposes 17 tools. Three are always registered. The rest are registered only while the sidecar has at least one component of the matching type, so an agent never sees tools it can’t use.
The schema the server sends when a client connects is the authoritative definition of each tool. This page is a readable summary of it.
Call get_components first
Component-specific tools take a component name as input, such as storeName or pubsubName. An agent should call get_components first to learn the names that exist, rather than guess. The server’s instructions to the model say the same thing, and tell it to pass metadata fields as JSON objects ({}), never as strings ("{}").
Safety properties
Each tool carries the MCP annotations readOnlyHint, destructiveHint, and idempotentHint, which MCP clients can use to decide whether to ask a person before a call. The tables below show them as:
Read-only: reads data and changes nothing.
Idempotent: calling it again with the same inputs has no further effect, so it is safe to retry.
Destructive: may delete or overwrite data, or trigger an action that can’t be undone. Consider asking a person to confirm the first call.
Side effect: changes state or triggers an action somewhere.
Every tool except get_components also sets openWorldHint, because it reaches systems outside the server.
Tools follow the components loaded in the sidecar. When component hot reloading adds the first component of a type, the server registers that type’s tools the next time a client lists tools or calls get_components, and notifies connected clients that the tool list changed. When the last one is removed, its tools are removed too. Clients that read the tool list only once need to reconnect to see the change.
A tool that fails, because an input is missing or the sidecar returns an error, returns a result marked as an error with a message the model can read, rather than failing the MCP request.
Metadata
get_components
Lists the components loaded in the sidecar, with each one’s name, type, version, and capabilities. Only component types the server has tools for are listed. The list is read live from the sidecar on every call.
Inputs: none.
Service invocation
invoke_service
Calls a method on another Dapr app through service invocation and returns the response body.
Input
Required
Description
appID
Yes
App ID of the target app.
method
Yes
Method or path to call, for example orders/status.
data
No
Request body, usually JSON.
httpVerb
No
GET, HEAD, POST, PUT, PATCH, DELETE, or OPTIONS. Default POST.
contentType
No
Content type of data. Default application/json if data is valid JSON, text/plain otherwise.
metadata
No
Headers to send to the target app. Keys starting with dapr-, grpc-, or : are rejected.
Actors
invoke_actor_method
Calls a method on a virtual actor and returns its response.
Reads every secret the app can access in a secret store. The tool description tells the model to use it only when the user explicitly asks for all secrets.
Input
Required
Description
storeName
Yes
Secret store component name.
metadata
No
Store-specific options.
Conversation
converse_with_llm
Sends a prompt to another LLM through a conversation component and returns the reply, along with a context_id to continue the conversation.
Input
Required
Description
name
Yes
Conversation component name.
prompt
Yes
Prompt to send.
contextId
No
context_id from an earlier call, to continue that conversation. A new one is generated when omitted.
temperature
No
Sampling temperature from 0.0 to 1.0. Default 0.7.
Cryptography
encrypt_data
Encrypts text with a key held by a cryptography component and returns the cipher text, base64-encoded.
Input
Required
Description
componentName
Yes
Cryptography component name.
plainText
Yes
Text to encrypt.
keyName
No
Key name, or name/version. Default rsa-private-key.pem.
keyWrapAlgorithm
No
Key wrap algorithm, for example RSA, RSA-OAEP-256, or A256KW. Default RSA.
decrypt_data
Decrypts cipher text produced by encrypt_data and returns the plain text.
Input
Required
Description
componentName
Yes
Cryptography component name.
cipherText
Yes
Base64-encoded cipher text.
keyName
No
Key name, or name/version. When omitted, the key named in the cipher text header is used.
Distributed lock
Always pair acquire_lock with release_lock, and set an expiry so a lock isn’t held forever if the agent stops.
acquire_lock
Tries to acquire a distributed lock on a resource. If another owner holds it, the call succeeds with lock_acquired: false.
Input
Required
Description
storeName
Yes
Lock store component name.
resourceID
Yes
Name of the resource to lock.
lockOwner
Yes
Unique ID of the caller, used again to release the lock.
expiryInSeconds
Yes
Lock lifetime in seconds, greater than zero. The lock expires automatically after this time.
release_lock
Releases a lock. Fails if the lock doesn’t exist or belongs to a different owner.
Input
Required
Description
storeName
Yes
Lock store component name.
resourceID
Yes
Name of the locked resource.
lockOwner
Yes
The owner ID used to acquire the lock.
5 - Dapr MCP server observability
Traces, metrics, and logs the Dapr MCP server exports over OpenTelemetry, and its health endpoints
The Dapr MCP server exports traces, metrics, and logs over the OpenTelemetry Protocol (OTLP), and serves health endpoints for probes when it runs over HTTP.
Turn on telemetry
Telemetry is off until you set an OTLP endpoint. Point the server at an OpenTelemetry Collector, or any backend that accepts OTLP:
An endpoint without a scheme, such as otel-collector:4317, uses TLS. Add OTEL_EXPORTER_OTLP_INSECURE=true to send plaintext to an endpoint like that, or give the scheme explicitly. To use OTLP over HTTP instead of gRPC, set OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf and use the HTTP port, usually 4318.
All three signals go to the same endpoint unless you override one with its OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_* variables. See the configuration reference for every setting. If the exporters can’t be set up, the server logs a warning and keeps running without telemetry.
A server span for each HTTP request, named after the method and route, for example POST /. It continues the caller’s trace when the request carries a W3C traceparent header.
A span for each tool call, named after the tool, for example save_state. It has the attributes mcp.tool.name, mcp.tool.package, and, where the tool targets a component, dapr.component.name, plus details such as the state key or topic. Failed calls are marked with an error status.
The tool span’s trace context is passed to the Dapr sidecar with each gRPC call, so spans from the sidecar, and from any app it calls, join the same trace when Dapr tracing exports to the same backend.
Metrics
Metrics are exported every 10 seconds by default (OTEL_METRIC_EXPORT_INTERVAL). Set DAPR_MCP_SERVER_METRICS_ENABLED=false to turn them off while keeping traces.
Metric
Type
Unit
Description
dapr-mcp-server.tool.invocations
Counter
{invocation}
Tool calls, by outcome.
dapr-mcp-server.tool.errors
Counter
{error}
Tool calls that failed.
dapr-mcp-server.tool.duration
Histogram
ms
Tool call duration.
dapr-mcp-server.tool.in_progress
Up-down counter
{tool}
Tool calls running now.
dapr-mcp-server.http.requests_total
Counter
{request}
MCP HTTP requests.
dapr-mcp-server.http.request_duration
Histogram
ms
MCP HTTP request duration. Server-sent event streams are counted but not timed, because their duration is the client’s session length.
dapr-mcp-server.http.requests_in_flight
Up-down counter
{request}
MCP HTTP requests being handled now.
Tool metrics carry only bounded attributes: tool.name and tool.package, plus outcome (success or error) on the invocation and duration metrics and error.type on the error counter. Values the agent supplies, such as component names, app IDs, and actor types, are kept off metrics and recorded on the tool’s span instead. HTTP metrics carry http.request.method, http.response.status_code, and http.route. The HTTP metrics cover MCP requests only, not the health endpoints.
Logs
The server writes structured JSON logs to stderr, never stdout, so logs can’t corrupt the stdio transport. Every record includes the service name and version. Set the level with DAPR_MCP_SERVER_LOG_LEVEL (debug, info, warn, or error; default info).
When an OTLP endpoint is set, the same records are also exported as OpenTelemetry logs, correlated with the active trace. Set DAPR_MCP_SERVER_LOGS_OTEL_ENABLED=false to keep logs on stderr only.
At debug level the server logs incoming request headers, with Authorization, Cookie, X-Api-Key, and the configured token header redacted. Each successful tool call is logged at info, and each failure at warn, with its target, such as the store name and key, but never with payloads or secret values.
Health endpoints
With --http, the server serves three endpoints on the same address as MCP. They never require authentication, and each returns JSON with a status of healthy or unhealthy and the server version.
Endpoint
Returns 200 when
Otherwise
/livez
The process is running.
—
/startupz
Startup has finished: the server connected to the sidecar and registered its tools.
503
/readyz
The server is ready and the Dapr sidecar answers a metadata request within 5 seconds. The response lists each check.
On SIGTERM or SIGINT, /readyz starts returning 503, open server-sent event streams are closed, and in-flight requests get up to 15 seconds to finish before the server exits.
The --health-check flag runs the same liveness check from the command line. It requests /livez on the address from --health-check-addr, or from --http, or localhost:8080, and exits with 0 or 1. The container image uses it as its HEALTHCHECK.
Over stdio there is no HTTP listener, so these endpoints aren’t available.
We use cookies to analyze site usage and improve your experience. You can accept or reject analytics cookies.