Post

Building an MCP Server: A 60-Minute Speedrun

Building an MCP Server: A 60-Minute Speedrun

The Challenge

My friend bet me I couldn’t build a useful MCP server in under an hour. Challenge accepted!

“MCP is complex,” he said. “You need to understand protocols, handle JSON-RPC, manage sessions…”

I set a timer for 60 minutes. Here’s what happened.

Minute 0-5: The Plan

MCP (Model Context Protocol) lets AI models use tools. I needed something actually useful. Then it hit me – every developer’s pain point:

An MCP server that manages my Google Cloud resources directly from any AI chat.

Imagine:

1
2
3
4
You: "Show me my GCP compute instances"
AI: *Actually lists your real instances*
You: "Stop the expensive one"
AI: *Actually stops it*

Let’s build this. ⏰

Minute 6-15: The Shortest MCP Server Ever

Started with TypeScript + Deno (because who has time for npm install?):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// mcp-gcp-server.ts
import { Server } from "https://deno.land/x/mcp/mod.ts";

const server = new Server({
  name: "gcp-manager",
  version: "1.0.0",
});

// The simplest tool possible
server.addTool({
  name: "list_instances",
  description: "List all GCP compute instances",
  inputSchema: {
    type: "object",
    properties: {
      project: { type: "string", description: "GCP project ID" }
    }
  },
  handler: async ({ project }) => {
    // We'll implement this in a minute
    return { instances: ["instance-1", "instance-2"] };
  }
});

server.start();
console.log("MCP Server running! 🚀");

Run it:

1
deno run --allow-net mcp-gcp-server.ts

IT WORKS! 15 minutes in, I have an MCP server. It does nothing useful, but it exists.

Minute 16-30: Making It Actually Useful

Time to add real GCP integration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { exec } from "https://deno.land/x/exec/mod.ts";

async function gcloudCommand(cmd: string) {
  const output = await exec(`gcloud ${cmd} --format=json`, { 
    output: "piped" 
  });
  return JSON.parse(output);
}

// Updated tool with real functionality
server.addTool({
  name: "list_instances",
  description: "List all GCP compute instances with their status",
  inputSchema: {
    type: "object",
    properties: {
      project: { 
        type: "string", 
        description: "GCP project ID (optional)" 
      }
    }
  },
  handler: async ({ project }) => {
    const projectFlag = project ? `--project=${project}` : "";
    const instances = await gcloudCommand(
      `compute instances list ${projectFlag}`
    );
    
    // Make it readable
    return {
      instances: instances.map(i => ({
        name: i.name,
        zone: i.zone.split('/').pop(),
        status: i.status,
        machineType: i.machineType.split('/').pop(),
        cost: calculateHourlyCost(i.machineType) // I wish GCP API had this
      }))
    };
  }
});

Minute 31-40: The Power Tools

Listing is boring. Let’s add POWER:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// Stop bleeding money
server.addTool({
  name: "stop_instance",
  description: "Stop a GCP compute instance to save costs",
  inputSchema: {
    type: "object",
    properties: {
      instance: { type: "string" },
      zone: { type: "string" }
    },
    required: ["instance", "zone"]
  },
  handler: async ({ instance, zone }) => {
    await gcloudCommand(
      `compute instances stop ${instance} --zone=${zone}`
    );
    return { 
      success: true, 
      message: `Stopped ${instance}. Your wallet thanks you. 💰` 
    };
  }
});

// The cost analyzer
server.addTool({
  name: "analyze_costs",
  description: "Analyze GCP costs and suggest optimizations",
  inputSchema: {
    type: "object",
    properties: {}
  },
  handler: async () => {
    const instances = await gcloudCommand("compute instances list");
    
    let totalMonthlyCost = 0;
    const suggestions = [];
    
    for (const instance of instances) {
      const hourlyCost = calculateHourlyCost(instance.machineType);
      const monthlyCost = hourlyCost * 730;
      totalMonthlyCost += monthlyCost;
      
      // Smart suggestions
      if (instance.status === "RUNNING" && 
          instance.name.includes("test")) {
        suggestions.push({
          instance: instance.name,
          action: "Consider stopping test instances",
          savings: `$${monthlyCost.toFixed(2)}/month`
        });
      }
      
      if (instance.machineType.includes("n2-standard-32")) {
        suggestions.push({
          instance: instance.name,
          action: "This seems oversized. Consider n2-standard-8?",
          savings: `$${(monthlyCost * 0.75).toFixed(2)}/month`
        });
      }
    }
    
    return {
      totalMonthlyCost: `$${totalMonthlyCost.toFixed(2)}`,
      suggestions
    };
  }
});

Minute 41-50: The Python Plot Twist

“But I prefer Python!” - Every data scientist ever.

Fine. Here’s the same thing in Python (in 5 minutes because Python is life):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# mcp_gcp_server.py
from mcp import Server, Tool
import subprocess
import json

server = Server("gcp-manager", "1.0.0")

def run_gcloud(command):
    result = subprocess.run(
        f"gcloud {command} --format=json",
        shell=True,
        capture_output=True,
        text=True
    )
    return json.loads(result.stdout)

@server.tool(
    description="List GCP instances",
    parameters={
        "type": "object",
        "properties": {
            "project": {"type": "string"}
        }
    }
)
async def list_instances(project=None):
    cmd = "compute instances list"
    if project:
        cmd += f" --project={project}"
    
    instances = run_gcloud(cmd)
    return {
        "instances": [
            {
                "name": i["name"],
                "status": i["status"],
                "zone": i["zone"].split("/")[-1],
                "type": i["machineType"].split("/")[-1]
            }
            for i in instances
        ]
    }

@server.tool(
    description="Emergency stop all instances",
    parameters={"type": "object", "properties": {}}
)
async def panic_button():
    """When you check your GCP bill at 3 AM"""
    instances = run_gcloud("compute instances list")
    stopped = []
    
    for instance in instances:
        if instance["status"] == "RUNNING":
            zone = instance["zone"].split("/")[-1]
            run_gcloud(
                f"compute instances stop {instance['name']} --zone={zone}"
            )
            stopped.append(instance["name"])
    
    return {
        "stopped": stopped,
        "message": f"Stopped {len(stopped)} instances. Crisis averted! 🚨"
    }

if __name__ == "__main__":
    server.run()

Minute 51-55: Testing with Real AI

Connected it to Claude via MCP client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from mcp_client import Client

async with Client("./mcp_gcp_server.py") as client:
    # List instances
    result = await client.call_tool(
        "list_instances",
        {"project": "my-startup-project"}
    )
    print(f"Found {len(result['instances'])} instances")
    
    # The panic button test (on dev environment!)
    if input("Test panic button? (y/n): ") == "y":
        result = await client.call_tool("panic_button", {})
        print(result["message"])

Minute 56-59: The Docker Finale

Because it’s 2025 and everything needs a container:

1
2
3
4
5
FROM denoland/deno:alpine
WORKDIR /app
COPY mcp-gcp-server.ts .
RUN deno cache mcp-gcp-server.ts
CMD ["run", "--allow-net", "--allow-run", "mcp-gcp-server.ts"]

Deploy to Cloud Run:

1
2
3
docker build -t mcp-gcp-server .
docker push gcr.io/my-project/mcp-gcp-server
gcloud run deploy mcp-gcp-server --image gcr.io/my-project/mcp-gcp-server

Minute 60: 🏁 DONE!

Timer stops. I have:

  • ✅ A working MCP server
  • ✅ Real GCP integration
  • ✅ Cost analysis tools
  • ✅ Emergency stop button
  • ✅ Both TypeScript and Python versions
  • ✅ Dockerized and deployed
  • ✅ My friend owing me lunch

The Lessons (Learned at Light Speed)

1. MCP is Surprisingly Simple

The protocol looks scary, but libraries handle the hard parts. Focus on your tools, not the plumbing.

2. Start with One Tool

Don’t build 20 tools. Build one that works, then iterate.

3. Real Integration > Demo Code

Actually connecting to real services (GCP in this case) makes it instantly useful.

4. The 80/20 Rule

80% of the value came from 20% of the code. The “list_instances” tool alone changed my workflow.

Beyond the Hour: What I Added Later

After winning the bet, I kept building:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// The money-saver
server.addTool({
  name: "schedule_shutdown",
  description: "Schedule instance to stop after work hours",
  handler: async ({ instance, time }) => {
    // Creates a Cloud Scheduler job
    // Saves hundreds per month
  }
});

// The debugger
server.addTool({
  name: "tail_logs",
  description: "Get recent logs from any GCP service",
  handler: async ({ service, lines = 50 }) => {
    // Real-time debugging from AI chat
  }
});

// The deployer
server.addTool({
  name: "deploy_cloud_run",
  description: "Deploy container to Cloud Run",
  handler: async ({ image, service }) => {
    // CI/CD from chat interface
  }
});

Your Turn: The 60-Minute Challenge

Here’s my challenge to you:

  1. Pick a problem (Your most annoying daily task)
  2. Set a timer (60 minutes, no cheating)
  3. Build an MCP server (Start with one tool)
  4. Share it (#MCPSpeedrun)

Starter template to beat my time:

1
2
3
git clone https://github.com/AIwithTim/mcp-speedrun-template
cd mcp-speedrun-template
# You have 60 minutes. GO!

The Real Magic

It’s not about building the perfect MCP server. It’s about realizing that the barrier between “I wish AI could do X” and “AI can now do X” is just 60 minutes of focused coding.

Every tool you build makes every AI model more powerful.

What will you build in your hour?


Update: My friend built an MCP server that orders pizza in 45 minutes. I’m not even mad. That’s genius.

Share your MCP speedrun results! Fastest working server gets featured in next week’s post. ⚡

This post is licensed under CC BY 4.0 by the author.