Multi-Cloud MCP Server
Multi-Cloud MCP Server
A local Model Context Protocol server that gives an LLM client (Claude Desktop, VS Code, Claude Code, etc.) tools to inspect and query your own GCP and AWS infrastructure: data warehouses, CI build systems, container/serverless runtimes, and their logs. You point it at your own project(s) and account(s) through a short interactive setup wizard. Nothing is hardcoded to any specific company, project, or account.
Table of contents
Quickstart
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python3 setup_wizard.py # interactive: configure your GCP and/or AWS environments
python3 server.py # runs the MCP server over stdioPoint your MCP client at server.py as the command to run (with the same .venv interpreter). For example, in a VS Code mcp.json or Claude Desktop config, the command is the absolute path to .venv/bin/python3 and the argument is the absolute path to server.py.
How it works
.env (written by setup_wizard.py)
|
v
config.py (parses .env into GCPEnvironment / AWSEnvironment objects)
|
v
server.py (registers tools for whichever providers have at least
| one configured environment, then serves over stdio)
-------------+-------------
| |
providers/gcp/ providers/aws/
one file per GCP service one file per AWS service
(bigquery, cloudbuild, (athena, codebuild, ecs,
cloudrun) lambda, s3, identity)Every tool function takes an optional env= argument naming which configured environment to act against (for example env="prod"). If you omit it, the tool falls back to whichever environment the wizard set as the default for that provider. Call the always-available list_environments tool to see what is currently configured and which environment is the default for each provider.
File by file
server.py: the entry point. At import time it readsconfig.CONFIGand registers only the tool modules for providers that actually have a configured environment (a GCP-only setup never exposes AWS tools, and vice versa). It also defines one provider-agnostic tool,list_environments, directly. If neither provider is configured it prints a message to stderr and exits with status 1, rather than hanging waiting for input, because the stdio transport is reserved for MCP protocol traffic once an MCP client has launched it.config.py: the configuration model. DefinesGCPEnvironmentandAWSEnvironmentdataclasses, anAppConfigregistry keyed by environment name,load_config(path)to parse a.envfile into anAppConfig,render_env_file(cfg)andsave_config(cfg, path)to write one back out, and aslug(name)helper that turns an arbitrary environment name into the uppercase, underscore-separated form used in.envkey names (so"client-a-prod"becomes the key prefixCLIENT_A_PROD).AppConfig.resolve_gcp(env)/resolve_aws(env)are what every tool calls to turn anenv=argument (orNone) into a concrete environment object, raising aValueErrorthat lists what is actually configured if the name does not exist.setup_wizard.py: the interactive onboarding flow. Run it any time, it loads whatever.envalready exists, lets you add, remove, or change the default environment for either provider through a numbered menu, verifies each new environment's credentials as you add it (a live BigQuery/STS call, non-fatal for GCP, prompts to save-anyway for AWS on failure), and rewrites.envfrom scratch on save.live_check.py: a standalone smoke test you can run any time against your real.envto confirm a configured environment's credentials still work, without needing the full pytest suite or any specific cloud resources to exist. See Testing below.providers/errors.py: a single decorator,friendly_errors, applied to every tool function. It catchesValueError(an unknown environment name, a missing key file) and any other exception (an expired credential, an API failure) and turns it into a plain string starting with"Error: ..."or"Error calling <tool>: ...", so a bad call comes back as readable text instead of a stack trace.providers/gcp/auth.py: resolves GCP credentials for aGCPEnvironment, either via Application Default Credentials (google.auth.default()) or a service-account JSON key file, and exposesget_access_token()for the one tool module that talks to a GCP REST API directly instead of through a client library.providers/gcp/bigquery_tools.py:bq_list_datasets,bq_list_tables,bq_describe_table,bq_query,bq_preview_table. Thin wrappers around thegoogle-cloud-bigqueryclient.providers/gcp/cloudbuild_tools.py:cloudbuild_list_builds,cloudbuild_get_logs,cloudbuild_describe_build. There is no official Cloud Build Python client used here; it calls the REST API directly with a bearer token fromauth.py, and fetches full build logs straight from the build's GCS logs bucket.providers/gcp/cloudrun_tools.py:cloudrun_list_services,cloudrun_describe_service,cloudrun_list_revisions,cloudrun_get_logs,cloudrun_get_error_logs. Usesgoogle-cloud-runfor service/revision metadata andgoogle-cloud-loggingfor log retrieval, filtered byseverity>=ERRORfor the error-only variant.providers/aws/auth.py: resolves aboto3.Sessionfor anAWSEnvironment, either via a named profile (or the default credential chain) or explicit static keys, plusverify_credentials()(a thinsts:GetCallerIdentitywrapper used by both the wizard and theaws_whoamitool).providers/aws/logs_helpers.py: one sharedfetch_log_events()function used by bothecs_tools.pyandlambda_tools.py, since both need the same "fetch recent CloudWatch Logs, optionally error-filtered" behavior. The error filter is a CloudWatch Logs filter pattern (?ERROR ?Error ?CRITICAL ?FATAL ?Exception ?Traceback), since CloudWatch has no built-in structured severity field the way GCP's logging API does.providers/aws/athena_tools.py:athena_list_databases,athena_list_tables,athena_describe_table,athena_query,athena_preview_table. This is the closest AWS equivalent to BigQuery: a serverless SQL engine over the Glue Data Catalog. Unlike BigQuery, a query is asynchronous (start, then poll for completion, then fetch results), soathena_querypolls every second for up to 30 seconds before giving up.providers/aws/codebuild_tools.py:codebuild_list_projects,codebuild_list_builds,codebuild_get_logs,codebuild_describe_build. The CodeBuild equivalent of the Cloud Build tools.providers/aws/ecs_tools.py:ecs_list_clusters,ecs_list_services,ecs_describe_service,ecs_list_tasks,ecs_get_logs,ecs_get_error_logs. Covers the container half of what Cloud Run does in one service on GCP; the log tools resolve the right CloudWatch log group automatically from the service's task definition.providers/aws/lambda_tools.py:lambda_list_functions,lambda_describe_function,lambda_list_versions,lambda_get_logs,lambda_get_error_logs. Covers the serverless-function half of Cloud Run's niche. A function's log group is always/aws/lambda/<function_name>, so no extra lookup is needed before fetching logs.providers/aws/s3_tools.py:s3_list_buckets,s3_list_objects. Bonus coverage beyond the GCP tool set's shape.providers/aws/identity_tools.py:aws_whoami. Bonus coverage: confirms which AWS account and identity a named environment actually resolves to, useful once you have several environments configured.
Configuring environments
An "environment" is a named GCP project or AWS account/region you want the server able to talk to. You can configure any number of them under any names you like: dev, prod, client-a-prod, whatever makes sense to you. Run the wizard to add one:
$ python3 setup_wizard.py
=== Multi-Cloud MCP Setup Wizard ===
Loaded 0 GCP environment(s): (none)
Loaded 0 AWS environment(s): (none)
Main menu:
1) Add a cloud environment
2) Remove an environment
3) Set default environment for a provider
4) Save and exit
5) Exit without saving
Choose [1-5]: 1
Provider?
1) gcp
2) aws
Choose [1-2]: 1
--- Add GCP environment ---
Environment name (e.g. 'dev', 'prod', 'client-a-prod'): prod
GCP project ID: my-real-project-id
Region [us-central1]:
Auth method:
1) Application Default Credentials (gcloud auth application-default login)
2) Service account JSON key file
Choose [1-2]: 1
Verifying...
Auth OK.
BigQuery smoke test OK.
Added GCP environment 'prod'.Re-run the wizard any time to add another environment, remove one, or change which one is the default. It always reloads whatever .env already exists first, so nothing you have already configured is lost.
GCP auth modes
Chosen per environment, in the wizard:
Application Default Credentials (ADC): run
gcloud auth application-default loginonce beforehand, or rely on the default service account if this is running inside GCP itself.Service account JSON key file: point the wizard at a downloaded key file. Useful if you do not have
gcloudinstalled at all. The path is stored in.envasGCP_ENV_<NAME>_CREDENTIALS_PATH.
AWS auth modes
Also chosen per environment, in the wizard:
Named profile (recommended): set one up first with
aws configureoraws configure sso. No secrets are ever written to.env, only the profile name.Access key and secret: pasted directly into
.envfor an environment with no AWS CLI configured at all..envis git-ignored and the wizard sets its file permissions to600, but a profile is still the safer choice when you have one available.
Tool catalog with examples
Every tool below also accepts an optional env argument; it is omitted from the example calls where the default environment is being used.
BigQuery (GCP)
bq_list_datasets()
→ Project: my-real-project-id
part_catalog
analytics
bq_query(sql="SELECT id, name FROM `my-real-project-id.part_catalog.part_level` LIMIT 2")
→ [
{"id": "A100", "name": "Bearing"},
{"id": "A101", "name": "Gasket"}
]Cloud Build (GCP)
cloudbuild_list_builds(branch="main", limit=5)
→ [
{"id": "8f2e...", "status": "SUCCESS", "branch": "main",
"createTime": "2026-09-01T10:02:00Z", "finishTime": "2026-09-01T10:06:00Z",
"logUrl": "https://console.cloud.google.com/cloud-build/builds/8f2e..."}
]Cloud Run (GCP)
cloudrun_get_error_logs(service_name="my-service", limit=20)
→ [
{"timestamp": "2026-09-01T10:05:12Z", "severity": "ERROR",
"message": "connection refused: upstream timeout"}
]Athena (AWS, BigQuery equivalent)
athena_query(sql="SELECT id, amount FROM salesdb.orders LIMIT 2")
→ [
{"id": "1", "amount": "9.99"},
{"id": "2", "amount": "14.50"}
]CodeBuild (AWS, Cloud Build equivalent)
codebuild_describe_build(build_id="my-project:abcd-1234")
→ {
"id": "my-project:abcd-1234", "status": "SUCCEEDED",
"startTime": "2026-09-01 10:00:00+00:00", "endTime": "2026-09-01 10:04:12+00:00",
"sourceVersion": "main",
"phases": [{"phaseType": "BUILD", "phaseStatus": "SUCCEEDED", "durationInSeconds": 42}]
}ECS / Fargate (AWS, Cloud Run container half)
ecs_get_error_logs(cluster="my-cluster", service_name="my-service")
→ [
{"timestamp": 1767283512000, "logStream": "ecs/app/abc123",
"message": "ERROR something broke"}
]Lambda (AWS, Cloud Run serverless half)
lambda_describe_function(function_name="my-function")
→ {
"name": "my-function", "runtime": "python3.12", "memorySize": 256, "timeout": 30,
"handler": "lambda_function.handler", "role": "arn:aws:iam::123456789012:role/lambda-role",
"lastModified": "2026-09-01T10:00:00.000+0000", "envVars": ["STAGE", "LOG_LEVEL"]
}S3 (AWS, bonus)
s3_list_objects(bucket="my-bucket", prefix="logs/2026/09/", limit=5)
→ [
{"key": "logs/2026/09/01.json", "size": 4021, "lastModified": "2026-09-01 00:05:00+00:00"}
]Identity (AWS, bonus)
aws_whoami()
→ Environment: prod
Account: 123456789012
ARN: arn:aws:iam::123456789012:role/my-role
Region: us-east-1Always available
list_environments()
→ GCP environments:
dev (default) - project: my-dev-project, region: us-central1
prod - project: my-real-project-id, region: us-central1
AWS environments:
staging (default) - region: us-east-1Extending with a new service
Every tool module follows the same shape: a register(mcp) function that defines its @mcp.tool()-decorated functions as closures, each wrapped with @friendly_errors, each resolving its environment through CONFIG.resolve_gcp(env) or CONFIG.resolve_aws(env). To add a new service:
Create
providers/gcp/<service>_tools.pyorproviders/aws/<service>_tools.pyfollowing that shape.Add it to the relevant import list and registration loop in
server.py.Write a test file under
tests/gcp/ortests/aws/using thegcp_config/aws_configfixtures described below.
Never hardcode a project ID, account ID, or region anywhere. Everything should flow through the resolved environment object.
Testing
The test suite mocks every cloud call, so it runs with no real GCP or AWS credentials and makes no real network calls (aside from AWS tests, which run entirely against moto's in-memory AWS simulation, still fully offline).
pip install -r requirements-dev.txt
python3 -m pytest tests/ -vtests/conftest.py provides two fixtures used throughout: gcp_config(module) and aws_config(module), which each monkeypatch a tool module's CONFIG to a fake, single-environment AppConfig. AWS tests wrap their body in moto's @mock_aws decorator and use fake static keys, so the real providers/aws/auth.py code path runs end to end against moto's simulated AWS rather than mocking authentication away. GCP has no equivalent in-memory emulator, so GCP tests mock at the client-class boundary instead (bigquery.Client, run_v2.ServicesClient, and so on).
tests/test_server.py and tests/test_setup_wizard.py run the real scripts as subprocesses in an isolated temporary working directory, since both have import-time or interactive side effects that are not safe to exercise by importing them directly inside the test process.
Live smoke test
The mocked suite proves the code is correct in isolation, but it can't catch things like a mistyped project ID, an API that isn't enabled, or an expired credential. live_check.py covers that gap: it reads your real .env and makes one real, read-only call per configured environment (an actual credential fetch plus a lightweight BigQuery call for GCP, an actual sts:GetCallerIdentity for AWS), so it exercises the exact same auth code path the real tools use, without needing you to have any specific resources (datasets, buckets, functions) already set up.
python3 live_check.py # check every configured environment
python3 live_check.py --env prod # check only one environment by name
python3 live_check.py --provider aws # check only one providerThis was run against a real GCP project and a real AWS account during development: BigQuery, S3, Lambda, ECS, CodeBuild, and Athena all round-tripped successfully end to end (each reporting empty results, since those particular resources had nothing in them yet), and Cloud Run / Cloud Build correctly surfaced real "API not enabled" errors as clean text through friendly_errors instead of a raw traceback, confirming the error-handling path works against real failures too, not just mocked ones.
Troubleshooting
"No cloud providers configured" on startup: run
python3 setup_wizard.py."Unknown GCP/AWS environment '...'": the error message lists what is actually configured; use one of those names, or add a new one with the wizard.
GCP ADC issues:
gcloud auth application-default login.AWS profile issues:
aws sts get-caller-identity --profile <name>to test outside the wizard.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/anandapurva55/multi-cloud-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server