This content originally appeared on DEV Community and was authored by Angel Jose VARGAS GUTIERREZ
description: Problemas técnicos reales al desplegar servidores MCP en Cloud Run
tags: mcp, cloudrun, serverless, review
Análisis Crítico: Servidores MCP en Google Cloud Run
Contexto
Revisé un tutorial sobre desplegar servidores MCP (Model Context Protocol) en Google Cloud Run. Encontré varios problemas técnicos críticos que impiden su funcionamiento real.
Problema #1: Transporte Incompatible
Error Fatal: El tutorial usa StdioServerTransport en Cloud Run.
Por qué falla:
- Cloud Run requiere HTTP/HTTPS
- StdioServerTransport usa stdin/stdout
- Resultado: servidor no funcional
Solución:
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
const app = express();
const PORT = process.env.PORT || 8080;
const server = new Server({
name: "weather-mcp-server",
version: "1.0.0"
});
app.get("/sse", async (req, res) => {
const transport = new SSEServerTransport("/messages", res);
await server.connect(transport);
});
app.post("/messages", express.json(), async (req, res) => {
// Manejar mensajes MCP
});
app.listen(PORT);
Problema #2: package.json Incompleto
El tutorial menciona npm run build pero nunca lo define.
Solución:
{
"name": "mcp-server",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/server.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^0.5.0",
"express": "^4.18.2"
},
"devDependencies": {
"typescript": "^5.3.3",
"@types/node": "^20.10.0"
}
}
Problema #3: Configuración Cliente Incorrecta
Error: Usa curl como comando MCP
{
"mcpServers": {
"weather": {
"command": "curl",
"args": [...]
}
}
}
Claude Desktop necesita un proceso stdio, no HTTP directo.
Solución: Proxy Local
// mcp-proxy.ts
import fetch from "node-fetch";
import { stdin, stdout } from "process";
const SERVER = process.env.MCP_SERVER_URL;
stdin.on("data", async (chunk) => {
const req = JSON.parse(chunk.toString());
const res = await fetch(`${SERVER}/messages`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(req)
});
const data = await res.json();
stdout.write(JSON.stringify(data) + "\n");
});
Configuración:
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["/path/to/mcp-proxy.js"],
"env": {
"MCP_SERVER_URL": "https://your-server.run.app"
}
}
}
}
Problema #4: Sin Seguridad
Tutorial menciona autenticación pero no la implementa.
API Keys:
function authenticateAPIKey(req, res, next) {
const apiKey = req.headers["x-api-key"];
const validKeys = process.env.API_KEYS?.split(",") || [];
if (!validKeys.includes(apiKey)) {
return res.status(403).json({ error: "Invalid key" });
}
next();
}
app.use("/sse", authenticateAPIKey);
app.use("/messages", authenticateAPIKey);
Problema #5: Dockerfile Básico
Versión Optimizada:
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json tsconfig.json ./
RUN npm ci
COPY src/ ./src/
RUN npm run build
FROM node:18-alpine
WORKDIR /app
RUN addgroup -g 1001 nodejs && adduser -S nodejs -u 1001
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production
USER nodejs
EXPOSE 8080
CMD ["node", "dist/server.js"]
Costos Reales
Cloud Run Pricing:
- 100K requests/mes
- 200ms promedio
- 512MB memoria
Costo: ~$0.55/mes
Free tier cubre:
- 2M requests/mes
- 360K vCPU-segundos
- 180K GiB-segundos
Para desarrollo: $0 
Tests Esenciales
import { describe, it, expect } from "vitest";
describe("MCP Server", () => {
it("lista herramientas", async () => {
const res = await server.handleRequest({
method: "tools/list"
});
expect(res.tools).toHaveLength(1);
});
it("maneja errores", async () => {
const res = await server.handleRequest({
method: "tools/call",
params: { arguments: { city: "" } }
});
expect(res.isError).toBe(true);
});
});
Caso de Uso: Firestore
import { Firestore } from "@google-cloud/firestore";
const db = new Firestore();
server.setRequestHandler(CallToolRequest, async (req) => {
if (req.params.name === "query_db") {
const { collection } = req.params.arguments;
const snapshot = await db.collection(collection).limit(10).get();
return {
content: [{
type: "text",
text: JSON.stringify(snapshot.docs.map(d => d.data()))
}]
};
}
});
Valoración
Problemas:
Transporte incompatible
Config cliente incorrecta
Sin seguridad implementada
Scripts faltantes
Fortalezas:
Buena estructura
Ejemplo claro
Menciona best practices
Puntuación: 6.5/10
Con correcciones: 9/10
Tiempo Real
- Básico funcional: 30-45 min
- Production-ready: 2-4 horas
- Completo con CI/CD: 1-2 días
La promesa de “10 minutos” es muy optimista.
Recursos
Conclusión
El tutorial tiene buenas intenciones pero fallas técnicas críticas. Con las correcciones aquí propuestas, puede convertirse en un recurso valioso para la comunidad MCP.
This content originally appeared on DEV Community and was authored by Angel Jose VARGAS GUTIERREZ