DEV Community

Angel Jose VARGAS GUTIERREZ
Angel Jose VARGAS GUTIERREZ

Posted on

Análisis Exhaustivo: Despliegue de Servidores MCP en Google Cloud Run

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);
Enter fullscreen mode Exit fullscreen mode

🚨 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"
  }
}
Enter fullscreen mode Exit fullscreen mode

🚨 Problema #3: Configuración Cliente Incorrecta

Error: Usa curl como comando MCP

{
  "mcpServers": {
    "weather": {
      "command": "curl",
      "args": [...]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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");
});
Enter fullscreen mode Exit fullscreen mode

Configuración:

{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": ["/path/to/mcp-proxy.js"],
      "env": {
        "MCP_SERVER_URL": "https://your-server.run.app"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

🚨 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);
Enter fullscreen mode Exit fullscreen mode

🚨 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"]
Enter fullscreen mode Exit fullscreen mode

💰 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);
  });
});
Enter fullscreen mode Exit fullscreen mode

🎯 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()))
      }]
    };
  }
});
Enter fullscreen mode Exit fullscreen mode

📊 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.

Top comments (0)