We stood up our first internal MCP server in an afternoon. Pointed @powerduck/openapi-mcp-server at our billing API's OpenAPI document, got a Node process listening on :3000, and wired it into Claude Desktop. It felt like magic. The agent could look up invoices, list subscriptions, refund a test charge. We high-fived.

Six weeks later, that process was a small operational burden nobody had budgeted for.

It was running on a developer's Mac mini behind the office firewall. When the Mac mini rebooted on a Tuesday patch, three engineers lost their MCP connection without realizing why — their agents started hallucinating invoice shapes from the training data instead of calling the tool. We got a Slack thread.

The token the server used to call billing expired every 90 days. When it did, every tool call returned 401, and the agent's error message was opaque: tool error: unexpected 401 from upstream. Nobody owned rotating it. We found out when a support engineer asked why the agent kept saying invoices didn't exist.

We also had to reason about CORS, session limits, idle reaping, and whether to expose stdio to Claude Desktop vs Streamable HTTP to Cursor. Every teammate needed a slightly different config. The README grew.

None of this was hard. It was just there, every week.

What we actually needed

An MCP server is doing one boring job: it receives a structured tool call (get_invoice with {id: "inv_123"}) and translates it into an HTTP call to the real API (GET /invoices/inv_123 with the right auth header). That translation is mechanical. The OpenAPI spec already encodes the path, the method, the parameter placement, and the response shape.

The things we were running the Node process for were:

  • keeping a process alive across reboots, laptops, and VPN flakiness;
  • holding the upstream credential somewhere central so rotation doesn't touch every config file;
  • serving a stable HTTPS URL that works from Cursor, Claude Desktop, CI, and a teammate's personal laptop;
  • logging which tools got called and redacting the sensitive headers.

That is not a weekend project. It is also not a service you want to hand-roll per internal API. We had billing, support tickets, our CRM, and a feature-flag service. Four servers, four sets of uptime alerts, four rotation calendars.

The switch

We moved to a hosted gateway instead: upload the OpenAPI doc, toggle which operations we want exposed, paste the upstream credential once, and get a public HTTPS MCP URL. The gateway translates tool calls into the same HTTP requests the Node process used to make — same path, same auth, same response. The agent side of the config shrank to one URL and (optionally) one access key.

{
  "mcpServers": {
    "billing": {
      "type": "http",
      "url": "https://mcp.powerduck.com/d/abc123",
      "headers": { "Authorization": "Bearer <team-access-key>" }
    }
  }
}

The Mac mini process is gone. The 90-day token lives in the gateway's config, rotates there, and never appears in anyone's ~/.cursor/mcp.json. When billing's spec adds a new endpoint, we re-upload the doc and the tool surface updates without a redeploy.

The honest caveat

A hosted gateway translates calls to a remote API. It does not mock, it does not hold state, and it does not run your service's logic locally. If your MCP tool needs to touch the filesystem, query a local database, or call something bound to localhost, you still want a local stdio server.

But for the long tail of internal HTTP APIs that the agent should be able to read from — billing, support, CRM, flags, search — the "run your own Node server" step is mostly tax. The spec is the contract. The gateway is the runtime. You want to spend your effort on which operations to expose and what credentials to attach, not on uptime alerts for a process that was never supposed to be a product.

If your MCP server has a Slack thread named after it, you have built too much infrastructure. The interesting part is the spec, not the Express app.

If you would rather not own another Node process and its uptime, Powerduck uploads your OpenAPI doc and hands you back a hosted Streamable HTTP MCP URL — no server to babysit.