Kav Pather

Jul 29, 2026 • 3 min read

How to build your own MCP server

One config. Your database or your API, as MCP tools

Most MCP tutorials hand you a Node project. Install an SDK, write a tool handler, wire stdio — and you end up with something that runs on your laptop, as you, for exactly one user.

Fine for a demo. Not something you can give a customer.

The other way: describe the tool in config, deploy, point Claude at a URL.

A tool is just a route

api/tasks:
 output: http
 method: POST
 mcp:
 enabled: true
 tool_name: list_tasks

 actions:
 - name: ValidateToken
 input: a|headers|
 assert:
 http_code_on_error: 401
 tests:
 - value: airpipe-jwt
 is_valid_jwt: a|ap_var::MCP_SECRET|

 - name: ListTasks
 run_when_succeeded: [ValidateToken]
 database: main
 query: |
 SELECT id, title, status
 FROM tasks
 ORDER BY created_at DESC
 LIMIT 200;

Verify the token, run the query. That's the tool.

Note what's missing: any MCP-specific auth. Air Pipe optionally takes the client's bearer token, forwards it into the interface, and runs the same actions an HTTP request would. Securing an MCP tool is securing a route.

The full file adds a CheckBody action — its assert tests become the tool's input schema and the OpenAPI request body. You describe the input once.

The database

global:
 databases:
 main:
 driver: postgres
 conn_string: "a|ap_var::DATABASE_URL|"

a|ap_var::DATABASE_URL| is a managed variable, so the config you share carries no credentials.

Installing the pack is about one click: it finds every ap_var:: the config needs, prompts for the missing ones, then deploys to staging and gives you the URLs.

Point Claude at it

{
 "mcpServers": {
 "my-tasks": {
 "url": "https://api.airpipe.io/<org>/<env>/mcp",
 "headers": {
 "Authorization": "Bearer <token>"
 }
 }
 }
}

Mint the token once at jwt.io — HS256, secret is your MCP_SECRET. Restart the client and the tool appears.

You also get an HTTP endpoint, OpenAPI docs, metrics and a trace per tool call. Same file, not a second project.

It doesn't have to be a database

Swap the query for an HTTP call and the tool doesn't change shape:

 - name: CallMyApi
 http:
 url: a|ap_var::MY_API_BASE|/v1/tasks
 method: GET
 bearer_auth: a|ap_var::MY_API_TOKEN|

Same token check, same schema, same tool. So the data can stay behind the service that already enforces your rules — which matters, because "we're not giving an AI our production database" is what usually kills this conversation.

The hole most MCP servers have

tools/call runs your code. tools/list doesn't.

Listing tools returns metadata, so the auth inside your handlers never fires for it. Anyone with the URL can enumerate every tool and its full schema. They can't call anything. They can read the map.

One line per tool closes it:

list_authorizer: authorize-discovery

That points at an interface which re-runs the same token check when a client lists tools. No valid token, no tools — not even the names. I checked it end to end: no token and bad token both return an empty array; valid token returns the tools.

Get it running

https://airpipe.io/marketplace/pack/mcp-quickstart

Four files. Install, set two variables, seed, paste the URL into your client.

Need per-customer isolation? MCP Postgres Starter is the same shape with tenant-scoped tokens and revocation: https://airpipe.io/marketplace/pack/mcp-postgres-starter/

You can hand-roll all this with the TypeScript SDK. You'll also hand-roll the auth, the tenant scoping, the discovery gate, the traces, and a REST API for whatever doesn't speak MCP. That's the trade.

Is tools/list open on your MCP server right now? Worth checking.

Air Pipe is on Peerlist if you want to look at the project or leave feedback:

https://peerlist.io/kav_pather/project/air-pipe

Join Kav on Peerlist!

Join amazing folks like Kav and thousands of other builders on Peerlist.

peerlist.io/

It’s available... this username is available! 😃

Claim your username before it's too late!

This username is already taken, you’re a little late.😐

0

1

0