MCP Deployment: What They Don't Tell You About Hosting Your MCP Server (From 3 Months of Production Pain)
Let me be honest with you — I've been building an MCP server for my 1,800-hour personal knowledge base for about three months now. I've written 75 articles about every aspect of it: design lessons, error handling gotchas, authentication headaches... you name it.
But I haven't really talked about deployment. And honestly? That's where most of the real pain happens.
So here's the thing — all those tutorials show you how to build an MCP server locally. They give you the code for endpoints, show you how to handle authentication, and send you on your way. But when it comes to actually getting that thing running 24/7 so your AI assistants can connect to it from anywhere... crickets.
I learned the hard way. Let me save you some of that pain.
The Problem: MCP Needs Public Access
Unlike your local development server, MCP servers need to be accessible by AI clients. If you're using Claude Desktop, you can run it locally. But if you want your MCP server to work with:
- Multiple AI clients (desktop + mobile + web)
- Team members or other people using your server
- Cloud-based AI assistants that can't access localhost
- 24/7 availability without keeping your computer awake
You need to deploy it somewhere public.
And that's where the fun begins.
My First Attempt: Heroku Free Dyno
I started with Heroku because it was easy. I'd used it before for side projects. Free tier, easy Git deploy, automatic HTTPS... what could go wrong?
Well... let's talk about it.
Pros:
- Super easy deployment (
git push heroku main) - Automatic HTTPS out of the box
- Free tier gets you started
- Scales if you need it (but you pay)
Cons:
- Free dynos sleep after 30 minutes of inactivity. That means when your AI client connects after 30 minutes, it has to wake up the dyno. This takes 10-30 seconds. And sometimes... it just fails.
- MCP clients don't handle long timeouts well. If the connection takes more than 10 seconds, they hang up.
- You get 550 hours of free runtime per month. That's only ~18 days. If you want 24/7, you need to pay ($5/month minimum).
- Ephemeral filesystem — any data you write disappears when the dyno restarts. Okay for stateless MCP servers that connect to an external database, but if you're storing simple markdown files locally... nope.
My experience: 3/10. Works for testing, but terrible for daily use. I got tired of "connection timed out" errors every time I wanted to search my knowledge base.
Here's what my Heroku Procfile looked like, just for reference:
web: java -jar target/mcp-knowledge-server-1.0.0.jar --port=$PORT
Not complicated. Just didn't work reliably for 24/7 use.
Second Attempt: Fly.io
I'd heard good things about Fly.io from friends doing side projects. So I gave it a shot.
Pros:
- Very easy deployment with
fly launch - Global anycast network — you can deploy close to your users
- Free tier allows one small shared CPU app
- Automatic HTTPS with Let's Encrypt
- Persistent volumes available for $0.15/GB/month
Cons:
- Free tier apps stop after 168 hours (a week) of inactivity. You need to hit them with a cron job to keep them awake.
- Persistent volumes are tied to a specific region and host — if Fly needs to move your app to another host, you have to manually move the volume.
- Pricing can be confusing — you pay for egress, storage, etc. But for small apps, it's still pretty cheap (~$2-3/month for what I was doing).
- The CLI is great, but debugging deployment issues can be tricky.
My experience: 6/10. Much better than Heroku for my use case. I got a persistent volume for my markdown files, it was faster than Heroku, and I got 24/7 for a couple bucks a month. The "stop after a week" thing was annoying but easily fixed with a free cron job that pings the URL every day.
Here's my fly.toml stripped down to the essentials, if you're curious:
app = "my-mcp-knowledge-server"
primary_region = "hkg" # closest to me in China
[http_service]
internal_port = 8080
force_https = true
[mounts]
source = "data"
destination = "/data"
And my Dockerfile (Fly uses Docker):
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
FROM eclipse-temurin:21-jre-jammy
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
This actually worked pretty well for a while. I had my knowledge base running on Fly for almost two months. But...
I ran into an issue where Fly's traffic routing from China was... spotty. Sometimes it would take 2-3 seconds to connect, sometimes it would time out completely. Since I'm in China, I really needed something with better connectivity here.
So I decided to try something closer to home.
Current Setup: Tencent Cloud Serverless Cloud Function (SCF)
Since I'm in China, Chinese cloud providers give better latency. I decided to try serverless because I don't get much traffic (just me using it a few times per day). Why pay for a whole VM when it's just me?
Pros:
- Pay-as-you-go — for my tiny usage, it's about $0.50/month. That's cheaper than coffee.
- Closer to me in China, so latency is great (50-100ms vs 300-500ms on Fly).
- Automatic scaling — if someone else suddenly starts using it, it just scales.
- Free tier has a lot of free invocations — you can run this basically for free.
- Supports persistent COS (object storage) for my markdown files.
Cons:
- Cold starts! If it hasn't run in a few minutes, it takes 2-5 seconds to start up. Still better than Heroku's 30-second cold start.
- Serverless means your app is stateless — you need to store data somewhere else (COS, database). Not a big deal, but you have to design for it.
- Chinese cloud has it's own quirks — everything is in Chinese, the console is overwhelming, API Gateway configuration is finicky.
- HTTPS is automatic, but getting a custom domain working takes some clicking around.
My experience: 7.5/10. For my use case (personal knowledge base, just me using it a few times a day), this is perfect. It's cheap, latency is good, cold starts are manageable. If you're in China, I'd recommend this. If you're not, Fly.io or another provider is probably easier.
Here's roughly how it's structured:
- SCF Function runs the Java Spring Boot app (yes, Java works on SCF with the custom runtime)
- API Gateway handles the HTTP requests, gives me a public HTTPS endpoint
- COS Bucket stores all my markdown files — the MCP server reads them from COS when searching
- Cloud Monitor pings the endpoint every 10 minutes to prevent it from being cold all the time
Does it add complexity? Yeah, a little. But for the price and latency, it's worth it for me.
What About VPS / Dedicated VM?
Of course, you can always just deploy to a regular VPS. I've done that for other projects. Let's break it down:
Pros:
- Full control — you can do whatever you want, install whatever you want
- No cold starts, no sleeping, no stopping — it's always running
- Predictable pricing — you pay the same every month
- Easy to set up with Docker
Cons:
- More expensive than serverless for low-traffic apps — $5-10/month minimum
- You have to maintain it — security updates, OS upgrades, monitoring
- You have to manage HTTPS yourself (though Let's Encrypt makes this pretty easy now)
- You need to set up firewall rules, reverse proxy with Nginx, etc.
My thoughts: If you're running multiple MCP servers or expect decent traffic, this is the way to go. For a personal side project that just you use... it's overkill and unnecessary expensive. But it's definitely the most straightforward option if you're comfortable with Linux.
Basic Nginx configuration for MCP server looks like this:
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Add SSL with Certbot, and you're good to go. Not rocket surgery.
The Ngrok Development Workflow
Okay, before you deploy anything to production... you need to test it, right? When I'm developing locally, I use ngrok to expose my local server to the internet so I can test with different MCP clients.
This is actually a game-changer.
Pros:
- One command:
ngrok http 8080gives you a public HTTPS URL in seconds - Perfect for testing with remote MCP clients while you're still developing
- Free tier works fine for development
- You can inspect all the traffic going through ngrok — super helpful for debugging
Cons:
- Free tier has session timeouts — the ngrok tunnel dies after a few hours
- The URL changes every time you restart ngrok — you have to update your client config every time
- Not for production — don't even think about it. Ngrok is for development, not production hosting.
My daily workflow:
- Start MCP server locally on port 8080
- Run
ngrok http 8080 - Copy the ngrok HTTPS URL into my MCP client config
- Test everything
- When it works, deploy to production
Honestly, if you're building an MCP server and you need to test with real clients, ngrok is non-negotiable. It's just too useful.
Hard Lessons I Learned the Hard Way
Okay, let's get into the stuff no one tells you. These are the mistakes I made that cost me hours of debugging.
1. Always Set a Read Timeout on Your Server
MCP clients are pretty impatient. If your search takes longer than 10 seconds, they'll hang up. But you know what's worse? If your server doesn't have a read timeout set, idle connections can pile up and eventually eat all your file descriptors.
In Spring Boot, I set this in application.properties:
server.tomcat.connection-timeout=20000
server.tomcat.threads.max=200
20 seconds connection timeout, max 200 threads. More than enough for my use case, and it keeps things stable.
2. Health Check Endpoints Are Your Friend
Most cloud providers expect a health check endpoint they can ping to make sure your app is alive. I added a simple one:
@RestController
public class HealthController {
@GetMapping("/health")
public ResponseEntity<String> health() {
return ResponseEntity.ok("OK");
}
}
That's it. Now your cloud provider can ping /health every minute to keep the app alive and detect outages. Worth its weight in gold.
3. CORS Preflight Requests Break Everything If You Mess Them Up
We talked about this in the authentication article, but it bears repeating. CORS preflight OPTIONS requests don't include authentication headers. If you require authentication for everything (including OPTIONS), CORS will break.
Make sure your CORS configuration allows OPTIONS without authentication:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "OPTIONS")
.allowedHeaders("*");
}
}
Yes, allowing * origins is okay for a public MCP server. If you're the only one using it, you can restrict it to specific origins, but honestly... it's not a big deal for personal use.
4. Logs Are Everything When Things Break
When your MCP server stops working and the client just says "connection failed", the first place you look is logs. Make sure you're logging properly. On most cloud platforms, you get free log storage for a certain amount of time. Use it.
On Fly.io, I just do:
fly logs -a my-mcp-app
And I can see what's going on. On SCF, it's in the console. The point is: log the important stuff — incoming requests, errors, slow queries. Don't be afraid to log more than you think you need. When something breaks, you'll thank me.
5. Start Simple, Then Optimize
I overcomplicated my deployment at first. I tried to set up Kubernetes because that's what I use at work. For a personal MCP server? That's ridiculous. Don't do what I did.
Start with the simplest thing that could possibly work:
- If you're just testing → ngrok
- If you're ready for production and it's just you → Fly.io or similar cheap platform
- If you need better latency in a specific region → check local cloud providers
- If you need to run multiple services → VPS with Docker Compose
So Where Should You Deploy Your MCP Server?
Honestly, it depends on your use case. Let me give you my recommendations:
| Use Case | Recommendation | Expected Cost |
|---|---|---|
| Local development only | ngrok | Free |
| Personal side project, just you | Fly.io | $2-5/month |
| Personal side project in China | Tencent Cloud SCF + COS | < $1/month |
| Multiple users / production | VPS with Docker + Nginx | $5-15/month |
| Experimenting / testing | Heroku | Free (but unreliable for 24/7) |
For what it's worth, I think Fly.io is the sweet spot for most people building an MCP server. It's easy, it's cheap, it's reliable enough. Unless you have specific needs (like being in China), just go with Fly.io. You'll be up and running in 10 minutes.
Wrapping Up
After three months of deploying and re-deploying my MCP server, the big lesson I learned is this: deployment isn't glamorous, but it matters. You can build the perfect MCP server with all the right features, but if it's not reliably accessible when your AI client needs it... what's the point?
I've gone from "this is cool but it never works when I need it" on Heroku, to "this works great most of the time" on Fly, to "this is perfect for my needs" on Tencent SCF. And I learned a ton along the way.
The MCP ecosystem is still young, right? Everyone's talking about building servers and clients, but no one's talking much about the boring stuff like deployment. But that's okay — that's why I'm writing this.
What's Your Experience?
I've tried a handful of approaches, but I know there are plenty of other options out there. Are you running an MCP server in production? Where are you hosting it? Have you found a deployment approach that works really well for you?
Drop a comment below and share what you've learned — I'd love to hear about it. I'm always looking for ways to make this more reliable (and cheaper).
If you found this helpful, check out the GitHub repository for my MCP-enabled knowledge base. It's open source, and you can see the full deployment configuration I use.
Top comments (1)
its a myth that ngrok has timeout limits, we used to do that on anon use but do not anymore. You sure you're authenticated?