Skip to content

Commit 46bb4cc

Browse files
authored
Add opentelemetry instrumentation instructions to cloud run MCP sample (#13991)
* Add opentelemetry instrumentation instructions to cloud run MCP sample * Review comments * Removed unneeded deps * Remove "GCP" * Fix region tags * Update to two samples with shared code Split the test_server.py into separate OTel script * Fix copyright and pin deps * Merge into one sample
1 parent b036c3d commit 46bb4cc

6 files changed

Lines changed: 406 additions & 83 deletions

File tree

run/mcp-server/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,32 @@ You should see the following output:
182182

183183
You have successfully deployed a remote MCP server to Cloud Run and tested it
184184
using the FastMCP client.
185+
186+
## Observability with OpenTelemetry
187+
188+
This sample includes integration with OpenTelemetry to send traces, logs, and metrics to Google
189+
Cloud Observability (Cloud Trace, Cloud Logging, and Cloud Monitoring).
190+
191+
[FastMCP is natively instrumented for
192+
OpenTelemetry](https://gofastmcp.com/servers/telemetry#opentelemetry), so simply setting up the
193+
SDK is enough to get telemetry data. Learn more about OpenTelemetry instrumentation
194+
[here](https://docs.cloud.google.com/stackdriver/docs/instrumentation/overview).
195+
196+
197+
### Setup Observability
198+
199+
1. **Ensure APIs are enabled**:
200+
Make sure you have enabled the Telemetry (OTLP) API, Cloud Logging API, and Cloud Monitoring API in your Google Cloud project.
201+
202+
```bash
203+
gcloud services enable logging.googleapis.com monitoring.googleapis.com telemetry.googleapis.com
204+
```
205+
206+
1. **Run the server**:
207+
The sample is pre-configured to use OpenTelemetry.
208+
209+
* **Locally**: Run `uv run server.py`. You can test it with the client: `uv run test_server.py`.
210+
* **Cloud Run**: Deploy using the instructions in the [Deploy](#deploy) section. The default `Dockerfile` is already set up to run the instrumented server.
211+
212+
1. **View Traces**:
213+
After interacting with the server to generate traces, you can view them in the Google Cloud Console. For detailed instructions, see the [Google Cloud Trace documentation on finding traces](https://docs.cloud.google.com/trace/docs/finding-traces).

run/mcp-server/otel_setup.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# [START cloudrun_mcpserver_setup_otel]
16+
import logging
17+
import google.auth
18+
import google.auth.transport.requests
19+
import grpc
20+
from google.auth.transport.grpc import AuthMetadataPlugin
21+
from opentelemetry import trace
22+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
23+
OTLPSpanExporter,
24+
)
25+
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
26+
from opentelemetry.sdk.trace import TracerProvider
27+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
28+
29+
logger = logging.getLogger(__name__)
30+
31+
32+
def setup_opentelemetry(service_name: str) -> None:
33+
"""Sets up OpenTelemetry to send traces to Google Cloud Observability."""
34+
credentials, project_id = google.auth.default()
35+
if not project_id:
36+
raise Exception("Could not determine Google Cloud project ID.")
37+
38+
resource = Resource.create(
39+
attributes={
40+
SERVICE_NAME: service_name,
41+
"gcp.project_id": project_id,
42+
}
43+
)
44+
45+
# Set up OTLP auth
46+
request = google.auth.transport.requests.Request()
47+
auth_metadata_plugin = AuthMetadataPlugin(credentials=credentials, request=request)
48+
channel_creds = grpc.composite_channel_credentials(
49+
grpc.ssl_channel_credentials(),
50+
grpc.metadata_call_credentials(auth_metadata_plugin),
51+
)
52+
53+
# Set up OpenTelemetry Python SDK
54+
tracer_provider = TracerProvider(resource=resource)
55+
tracer_provider.add_span_processor(
56+
BatchSpanProcessor(
57+
OTLPSpanExporter(
58+
credentials=channel_creds,
59+
endpoint="https://telemetry.googleapis.com:443/v1/traces",
60+
)
61+
)
62+
)
63+
trace.set_tracer_provider(tracer_provider)
64+
logger.info("OpenTelemetry successfully initialized.")
65+
66+
67+
# [END cloudrun_mcpserver_setup_otel]

run/mcp-server/pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,10 @@ readme = "README.md"
66
requires-python = ">=3.10"
77
dependencies = [
88
"fastmcp==3.2.0",
9+
"opentelemetry-api==1.40.0",
10+
"opentelemetry-sdk==1.40.0",
11+
"opentelemetry-exporter-otlp-proto-grpc==1.40.0",
12+
"google-auth==2.49.1",
13+
"grpcio==1.80.0",
914
]
15+

run/mcp-server/server.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
# [START cloudrun_mcpserver_otel]
16+
from otel_setup import setup_opentelemetry
17+
setup_opentelemetry("mcp-server")
18+
1519
# [START cloudrun_mcpserver]
1620
import asyncio
1721
import logging
@@ -64,3 +68,4 @@ def subtract(a: int, b: int) -> int:
6468
)
6569

6670
# [END cloudrun_mcpserver]
71+
# [END cloudrun_mcpserver_otel]

run/mcp-server/test_server.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
# [START cloudrun_mcpserver_test_otel]
16+
from otel_setup import setup_opentelemetry
17+
setup_opentelemetry("test-server")
18+
1519
# [START cloudrun_mcpserver_test]
1620
import asyncio
1721

1822
from fastmcp import Client
1923

24+
2025
async def test_server():
2126
# Test the MCP server using streamable-http transport.
2227
# Use "/sse" endpoint if using sse transport.
@@ -28,12 +33,14 @@ async def test_server():
2833
# Call add tool
2934
print(">>> 🪛 Calling add tool for 1 + 2")
3035
result = await client.call_tool("add", {"a": 1, "b": 2})
31-
print(f"<<< ✅ Result: {result[0].text}")
36+
print(f"<<< ✅ Result: {result.content[0].text}")
3237
# Call subtract tool
3338
print(">>> 🪛 Calling subtract tool for 10 - 3")
3439
result = await client.call_tool("subtract", {"a": 10, "b": 3})
35-
print(f"<<< ✅ Result: {result[0].text}")
40+
print(f"<<< ✅ Result: {result.content[0].text}")
41+
3642

3743
if __name__ == "__main__":
3844
asyncio.run(test_server())
3945
# [END cloudrun_mcpserver_test]
46+
# [END cloudrun_mcpserver_test_otel]

0 commit comments

Comments
 (0)