|
| 1 | +# Cloud Run MCP Server Sample |
| 2 | + |
| 3 | +This sample shows how to deploy a remote MCP server to Cloud Run. |
| 4 | + |
| 5 | +This sample uses the `streamable-http` transport, which allows for running MCP |
| 6 | +servers remotely. You can read more about MCP transports in the |
| 7 | +[official MCP docs](https://modelcontextprotocol.io/docs/concepts/architecture#transport-layer). |
| 8 | + |
| 9 | +## Benefits of running an MCP server remotely |
| 10 | + |
| 11 | +Running an MCP server remotely on Cloud Run can provide several benefits: |
| 12 | + |
| 13 | +- **📈 Scalability**: Cloud Run is built to [rapidly scale out to handle all incoming requests](https://cloud.google.com/run/docs/about-instance-autoscaling). |
| 14 | +Cloud Run will scale your MCP server automatically based on demand. |
| 15 | +- **👥 Centralized server**: You can share access to a centralized MCP server |
| 16 | +with team members through IAM privileges, allowing them to connect to it from |
| 17 | +their local machines instead of all running their own servers locally. If a |
| 18 | +change is made to the MCP server, all team members will benefit from it. |
| 19 | +- **🔐 Security**: Cloud Run provides an easy way to force authenticated |
| 20 | +requests. This allows only secure connections to your MCP server, preventing |
| 21 | +unauthorized access. |
| 22 | + |
| 23 | +> [!IMPORTANT] |
| 24 | +> The security aspect mentioned above is critical. If you don't enforce |
| 25 | +authentication, anyone on the public internet can potentially access and |
| 26 | +call your MCP server. |
| 27 | + |
| 28 | +## Math MCP Server |
| 29 | + |
| 30 | +LLMs are great at **non-deterministic tasks**: understanding intent, generating |
| 31 | +creative text, summarizing complex ideas, and reasoning about abstract |
| 32 | +concepts. However, they are notoriously unreliable for **deterministic tasks** |
| 33 | +– things that have one, and only one, correct answer. |
| 34 | + |
| 35 | +Enabling LLMs with **deterministic tools** (such as math operations) is one |
| 36 | +example of how tools can provide valuable context to improve the use of LLMs |
| 37 | +using MCP. |
| 38 | + |
| 39 | +This sample uses [FastMCP](https://gofastmcp.com/getting-started/welcome) to create |
| 40 | +a simple math MCP server that has two tools: `add` and `subtract`. FastMCP |
| 41 | +provides a fast, Pythonic way to build MCP servers and clients. |
| 42 | + |
| 43 | + |
| 44 | +## Prerequisites |
| 45 | + |
| 46 | +- Python 3.10+ |
| 47 | +- Uv (for package and project management, see [docs for installation](https://docs.astral.sh/uv/getting-started/installation/)) |
| 48 | +- Google Cloud SDK (gcloud) |
| 49 | + |
| 50 | +## Setup |
| 51 | + |
| 52 | +Set your Google Cloud credentials and project. |
| 53 | + |
| 54 | +```bash |
| 55 | +gcloud auth login |
| 56 | +export PROJECT_ID=<your-project-id> |
| 57 | +gcloud config set project $PROJECT_ID |
| 58 | +``` |
| 59 | + |
| 60 | +## Deploy |
| 61 | + |
| 62 | +You can deploy directly from source or using a container image. |
| 63 | + |
| 64 | +Both options use the `--no-allow-unauthenticated` flag to require authentication. |
| 65 | + |
| 66 | +This is important for security reasons. If you don't require authentication, |
| 67 | +anyone can call your MCP server and potentially cause damage to your system. |
| 68 | + |
| 69 | +<details open> |
| 70 | +<summary>Option 1 - Deploy from source</summary> |
| 71 | + |
| 72 | +```bash |
| 73 | +gcloud run deploy mcp-server --no-allow-unauthenticated --region=us-central1 --source . |
| 74 | +``` |
| 75 | + |
| 76 | +</details> |
| 77 | + |
| 78 | +<details> |
| 79 | +<summary>Option 2 - Deploy from a container image</summary> |
| 80 | + |
| 81 | +Create an Artifact Registry repository to store the container image. |
| 82 | + |
| 83 | +```bash |
| 84 | +gcloud artifacts repositories create mcp-servers \ |
| 85 | + --repository-format=docker \ |
| 86 | + --location=us-central1 \ |
| 87 | + --description="Repository for remote MCP servers" \ |
| 88 | + --project=$PROJECT_ID |
| 89 | +``` |
| 90 | + |
| 91 | +Build the container image and push it to Artifact Registry with Cloud Build. |
| 92 | + |
| 93 | +```bash |
| 94 | +gcloud builds submit --region=us-central1 --tag us-central1-docker.pkg.dev/$PROJECT_ID/mcp-servers/mcp-server:latest |
| 95 | +``` |
| 96 | + |
| 97 | +Deploy the container image to Cloud Run. |
| 98 | + |
| 99 | +```bash |
| 100 | +gcloud run deploy mcp-server \ |
| 101 | + --image us-central1-docker.pkg.dev/$PROJECT_ID/mcp-servers/mcp-server:latest \ |
| 102 | + --region=us-central1 \ |
| 103 | + --no-allow-unauthenticated |
| 104 | +``` |
| 105 | + |
| 106 | +</details> |
| 107 | + |
| 108 | +If your service has successfully deployed you will see a message like the following: |
| 109 | + |
| 110 | +```bash |
| 111 | +Service [mcp-server] revision [mcp-server-12345-abc] has been deployed and is serving 100 percent of traffic. |
| 112 | +``` |
| 113 | + |
| 114 | +## Authenticating MCP Clients |
| 115 | + |
| 116 | +Since you specified `--no-allow-unauthenticated` to require authentication, any |
| 117 | +MCP client connecting to the remote MCP server will need to authenticate. |
| 118 | + |
| 119 | +The official docs for [Host MCP servers on Cloud Run](https://cloud.google.com/run/docs/host-mcp-servers#authenticate_mcp_clients) |
| 120 | +provides more information on this topic depending on where the MCP client is |
| 121 | +running. |
| 122 | + |
| 123 | +For this sample, run the [Cloud Run proxy](https://cloud.google.com/sdk/gcloud/reference/run/services/proxy) |
| 124 | +to create an authenticated tunnel to the remote MCP server on your local |
| 125 | +machine. |
| 126 | + |
| 127 | +By default, the URL of Cloud Run service requires all requests to be |
| 128 | +authorized with the [Cloud Run Invoker](https://cloud.google.com/run/docs/securing/managing-access#invoker) |
| 129 | +(`roles/run.invoker`) IAM role. This IAM policy binding ensures that a |
| 130 | +strong security mechanism is used to authenticate your local MCP client. |
| 131 | + |
| 132 | +You should make sure that you or any team members trying to access the remote |
| 133 | +MCP server have the `roles/run.invoker` IAM role bound to their Google Cloud |
| 134 | +account. |
| 135 | + |
| 136 | +> [!TIP] |
| 137 | +> The below command may prompt you to download the Cloud Run proxy if it is |
| 138 | +> not already installed. Follow the prompts to download and install it. |
| 139 | +
|
| 140 | +```bash |
| 141 | +gcloud run services proxy mcp-server --region=us-central1 |
| 142 | +``` |
| 143 | + |
| 144 | +You should see the following output: |
| 145 | + |
| 146 | +```bash |
| 147 | +Proxying to Cloud Run service [mcp-server] in project [<YOUR_PROJECT_ID>] region [us-central1] |
| 148 | +http://127.0.0.1:8080 proxies to https://mcp-server-abcdefgh-uc.a.run.app |
| 149 | +``` |
| 150 | + |
| 151 | +All traffic to `http://127.0.0.1:8080` will now be authenticated and forwarded to |
| 152 | +the remote MCP server. |
| 153 | + |
| 154 | +## Testing the remote MCP server |
| 155 | + |
| 156 | +To test the remote MCP server use the |
| 157 | +[test_server.py](test_server.py) test script. It uses the FastMCP client to |
| 158 | +connect to `http://127.0.0.1:8080/mcp` (note the `/mcp` at the end for the |
| 159 | +`streamable-http` transport) and calls the `add` and `subtract` tools. |
| 160 | + |
| 161 | +> [!NOTE] |
| 162 | +> Make sure you have the Cloud Run proxy running before running the test server. |
| 163 | +
|
| 164 | +In a **new terminal** run: |
| 165 | + |
| 166 | +```bash |
| 167 | +uv run test_server.py |
| 168 | +``` |
| 169 | + |
| 170 | +You should see the following output: |
| 171 | + |
| 172 | +```bash |
| 173 | +>>> 🛠️ Tool found: add |
| 174 | +>>> 🛠️ Tool found: subtract |
| 175 | +>>> 🪛 Calling add tool for 1 + 2 |
| 176 | +<<< ✅ Result: 3 |
| 177 | +>>> 🪛 Calling subtract tool for 10 - 3 |
| 178 | +<<< ✅ Result: 7 |
| 179 | +``` |
| 180 | + |
| 181 | +You have successfully deployed a remote MCP server to Cloud Run and tested it |
| 182 | +using the FastMCP client. |
0 commit comments