-
Notifications
You must be signed in to change notification settings - Fork 16
Bug Bash 2026-08: Street View Imagery Insights MCP Server #82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/street-view-samples
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,14 @@ | ||
| #!/bin/bash | ||
| set -e | ||
|
|
||
| PROJECT_ID="${GOOGLE_CLOUD_PROJECT:-YOUR_PROJECT_ID}" | ||
| PROJECT_ID="${GOOGLE_CLOUD_PROJECT:-$(gcloud config get-value project 2>/dev/null)}" | ||
| PROJECT_ID="${PROJECT_ID:-YOUR_PROJECT_ID}" | ||
| REGION="us-central1" | ||
| SERVICE_NAME="streetview-imagery-insights-mcp" | ||
| IMAGE_TAG="us-central1-docker.pkg.dev/${PROJECT_ID}/cloud-run-source-deploy/${SERVICE_NAME}:latest" | ||
|
|
||
| echo "Building container image using Cloud Build..." | ||
| gcloud builds submit --tag "${IMAGE_TAG}" --project "${PROJECT_ID}" . | ||
|
|
||
| echo "Deploying to Cloud Run service: ${SERVICE_NAME}..." | ||
| echo "Building container and deploying to Cloud Run service: ${SERVICE_NAME} (project: ${PROJECT_ID})..." | ||
| gcloud run deploy "${SERVICE_NAME}" \ | ||
| --image "${IMAGE_TAG}" \ | ||
| --source . \ | ||
| --project "${PROJECT_ID}" \ | ||
| --region "${REGION}" \ | ||
| --allow-unauthenticated \ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please verify that this deployment to Cloud Run will work? (ie. deploy it and connect with Jetski CLI to successfully run a specific skill)
In particular, the service account will require the bigquery.jobs.create permission which is typically granted through one of the
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,11 +8,10 @@ | |
| from google.cloud import storage | ||
| from google import genai | ||
| from google.genai import types | ||
| from mcp.server.fastmcp import FastMCP | ||
| import uvicorn | ||
| from fastmcp import FastMCP | ||
|
|
||
| # Initialize FastMCP Server | ||
| mcp = FastMCP("Street View Imagery Insights", host="0.0.0.0") | ||
| mcp = FastMCP("Street View Imagery Insights") | ||
|
|
||
| CACHE_DIR = "/tmp/mcp_image_cache" | ||
| os.makedirs(CACHE_DIR, exist_ok=True) | ||
|
|
@@ -109,6 +108,22 @@ def equirectangular_to_perspective(src_image_path, heading_deg, pitch_deg, fov_d | |
| out_data = src_data[px_y, px_x] | ||
| return Image.fromarray(out_data) | ||
|
|
||
| def resolve_dataset(dataset_ref: str, default_project: str) -> tuple[str, str]: | ||
| """Resolves dataset reference to project and dataset IDs. | ||
|
|
||
| Handles: | ||
| - "dataset_id" -> (default_project, "dataset_id") | ||
| - "project_id.dataset_id" -> ("project_id", "dataset_id") | ||
| - "project_id:dataset_id" -> ("project_id", "dataset_id") | ||
| """ | ||
| if "." in dataset_ref: | ||
| project_id, dataset_id = dataset_ref.split(".", 1) | ||
| return project_id, dataset_id | ||
| elif ":" in dataset_ref: | ||
| project_id, dataset_id = dataset_ref.split(":", 1) | ||
| return project_id, dataset_id | ||
| return default_project, dataset_ref | ||
|
|
||
| # --- MCP Tools --- | ||
|
|
||
| @mcp.tool() | ||
|
|
@@ -124,7 +139,7 @@ def list_assets( | |
| List assets in the dataset, optionally filtered by asset class or geographic proximity. | ||
| """ | ||
| client_bq = bigquery.Client() | ||
| project_id = client_bq.project | ||
| project_id, dataset_id = resolve_dataset(dataset_id, client_bq.project) | ||
|
|
||
| where_clauses = [] | ||
| query_params = [] | ||
|
|
@@ -145,7 +160,7 @@ def list_assets( | |
|
|
||
| query = f""" | ||
| SELECT asset_id, asset_type, location, detection_time | ||
| FROM `{project_id}.{dataset_id}.all_assets` | ||
| FROM `{project_id}.{dataset_id}.latest_assets` | ||
| {where_str} | ||
| LIMIT @limit | ||
| """ | ||
|
|
@@ -171,11 +186,11 @@ def get_asset_observations(dataset_id: str, asset_id: str) -> str: | |
| Retrieve all historical observations for a specific asset ID. | ||
| """ | ||
| client_bq = bigquery.Client() | ||
| project_id = client_bq.project | ||
| project_id, dataset_id = resolve_dataset(dataset_id, client_bq.project) | ||
|
|
||
| query = f""" | ||
| SELECT observation_id, gcs_uri, bbox, pano_id, capture_time, camera_pose, asset_type | ||
| FROM `{project_id}.{dataset_id}.all_observations` | ||
| FROM `{project_id}.{dataset_id}.latest_observations` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be Reasons:
|
||
| WHERE asset_id = @asset_id | ||
| ORDER BY capture_time DESC | ||
| """ | ||
|
|
@@ -212,11 +227,11 @@ def analyze_cropped_asset( | |
| The raw image never leaves GCP. | ||
| """ | ||
| client_bq = bigquery.Client() | ||
| project_id = client_bq.project | ||
| project_id, dataset_id = resolve_dataset(dataset_id, client_bq.project) | ||
|
|
||
| query = f""" | ||
| SELECT gcs_uri, bbox, asset_type | ||
| FROM `{project_id}.{dataset_id}.all_observations` | ||
| FROM `{project_id}.{dataset_id}.cropped_observations_all` | ||
| WHERE observation_id = @observation_id | ||
| LIMIT 1 | ||
| """ | ||
|
|
@@ -301,11 +316,11 @@ def analyze_full_frame_context( | |
| The raw image never leaves GCP. | ||
| """ | ||
| client_bq = bigquery.Client() | ||
| project_id = client_bq.project | ||
| project_id, dataset_id = resolve_dataset(dataset_id, client_bq.project) | ||
|
|
||
| query = f""" | ||
| SELECT gcs_uri, bbox, asset_type | ||
| FROM `{project_id}.{dataset_id}.all_observations` | ||
| FROM `{project_id}.{dataset_id}.full_frame_observations_all` | ||
| WHERE observation_id = @observation_id | ||
| LIMIT 1 | ||
| """ | ||
|
|
@@ -404,7 +419,7 @@ def analyze_panorama_perspective( | |
| The raw image never leaves GCP. | ||
| """ | ||
| client_bq = bigquery.Client() | ||
| project_id = client_bq.project | ||
| project_id, dataset_id = resolve_dataset(dataset_id, client_bq.project) | ||
|
|
||
| query = f""" | ||
| SELECT gcs_uri | ||
|
|
@@ -455,9 +470,7 @@ def analyze_panorama_perspective( | |
| except Exception as e: | ||
| return json.dumps({"error": f"Failed to run model analysis: {str(e)}"}) | ||
|
|
||
| # Mount SSE transport Starlette app | ||
| app = mcp.sse_app() | ||
|
|
||
| if __name__ == "__main__": | ||
| port = int(os.getenv("PORT", 8080)) | ||
| uvicorn.run(app, host="0.0.0.0", port=port) | ||
| transport = os.getenv("MCP_TRANSPORT", "sse") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
See other comment. I recommend switching this to the |
||
| mcp.run(transport=transport, host="0.0.0.0", port=port) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
| mcp | ||
| google-genai | ||
| google-cloud-bigquery | ||
| google-cloud-storage | ||
| pillow | ||
| numpy | ||
| uvicorn | ||
| fastapi | ||
| mcp ~= 1.29 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can remove
|
||
| fastmcp ~= 3.4.7 | ||
| google-genai ~= 2.17 | ||
| google-cloud-bigquery ~= 3.43 | ||
| google-cloud-storage ~= 3.13.1 | ||
| pillow ~= 12.3 | ||
| numpy ~= 2.5.2 | ||
| fastapi ~= 0.141.1 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Remove this step
cd ...as the local development did not include it.