# Show HN: MCP-Proxy Add MCP support to any REST API – no code changes required

> Source: <https://github.com/crhuber/mcp-proxy>
> Published: 2026-08-18 10:36:18+00:00

Add MCP support to any REST API - no code changes required.

We've spent decades building REST APIs. Adding MCP support shouldn't mean
reimplementing your business logic. `mcp-proxy`

exposes your existing REST
endpoints as MCP tools automatically, with zero changes to your API.

`mcp-proxy`

loads a YAML config describing one or more upstream REST APIs, dynamically registers MCP tools for them, and translates each
tool call into an HTTP request against the real upstream API and back.

``` php
flowchart LR
    A[MCP Client] -- MCP tool call --> B[mcp-proxy]
    B -- HTTP request --> C[REST API]
    C -- HTTP response --> B
    B -- MCP tool result --> A
```

`mcp-proxy`

can be deployed in a sidecar pattern in Kubernetes or infront of any API service that doesnt provide a native MCP server.

Say for example, you already have an REST API server at [https://api.gold-api.com](https://api.gold-api.com) but you want to be able to query it via MCP.

To achieve this, create a `config.yaml`

```
endpoints:
  - name: gold-api
    upstream:
      base_url: "https://api.gold-api.com"
      timeout: 10s
      auth:
        type: "none"
    tools:
      - name: getPrice
        description: "Fetch gold price"
        http:
          method: GET
          path: "/price/XAU"
```

Run the server.

```
mcp-proxy --config config.yaml
```

What happens here is a new MCP tool called `getPrice`

is registered on the server. When this MCP tool is called the server makes a `HTTP GET`

request to `https://api.gold-api.com/price/XAU`

and returns the result to the MCP Client.

Verify its working by making MCP tool call using `curl`

```
curl -X POST http://localhost:8080/mcp \
        -H "Content-Type: application/json" \
        -H "Accept: application/json, text/event-stream" \
        -d '{
      "jsonrpc": "2.0",
      "id": 1,
      "method": "tools/call",
      "params": {
        "name": "gold-api_getPrice",
        "arguments": {}
      }
    }'
```

Response:

```
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"{\"currency\":\"USD\",\"price\":4409.299805}"}],"structuredContent":{"currency":"USD","price":4409.299805}}}
```

Congrats! We have now adapted our API to be MCP ready with no code changes to the API!

```
go install github.com/crhuber/mcp-proxy/cmd/mcp-proxy@latest
```

Or build from source:

```
go build -o mcp-proxy ./cmd/mcp-proxy
```

Or Use [kelp](https://github.com/crhuber/kelp)

```
kelp add crhuber/mcp-proxy --install
mcp-proxy --config config.yaml
```

| Flag | Env var | Default | Description |
|---|---|---|---|
`--config` |
`MCP_PROXY_CONFIG` |
(required) |
Path to the proxy config YAML file |
`--listen` |
`MCP_PROXY_LISTEN_ADDR` |
`:8080` |
Address to listen on |
`--auth-mode` |
`MCP_PROXY_AUTH_MODE` |
`none` |
`none` | `bearer` — whether the proxy's own `/mcp` endpoint requires a bearer token |
`--log-level` |
`MCP_PROXY_LOG_LEVEL` |
`info` |
`debug` | `info` | `warn` | `error` |
`--shutdown-grace` |
`MCP_PROXY_SHUTDOWN_GRACE` |
`15s` |
How long to wait for in-flight requests to finish on shutdown |
`--disable-localhost-protection` |
`MCP_PROXY_DISABLE_LOCALHOST_PROTECTION` |
`false` |
Disable the MCP SDK's DNS-rebinding Host header check |

When `--auth-mode=bearer`

, set `MCP_PROXY_BEARER_TOKEN`

in the environment
(it is intentionally not a flag so it never shows up in `--help`

output or
process argv).

The MCP SDK auto-enables DNS-rebinding protection: if the TCP connection it
accepted came in on a loopback address, it requires the request's `Host`

header to also look like loopback (`localhost`

, `127.0.0.0/8`

, `::1`

), and
otherwise rejects it with `403 Forbidden: invalid Host header`

. This guards
local companion services against a browser being tricked into hitting them,
and doesn't apply to mcp-proxy's deployment model.

If a sidecar connects to mcp-proxy over `127.0.0.1`

but forwards the
original external `Host`

header (e.g. `mcp.domain.com`

) — the accepted
connection's local address is loopback regardless of what address mcp-proxy
itself binds to, so changing `--listen`

/`MCP_PROXY_LISTEN_ADDR`

alone won't
fix it. Set `--disable-localhost-protection`

(or
`MCP_PROXY_DISABLE_LOCALHOST_PROTECTION=1`

) in that topology.

The config file describes one or more upstream endpoints and the tools that
should be generated for each. See [ config.yaml.example](/crhuber/mcp-proxy/blob/main/docs/config.yaml.example)
for a full annotated example, including path/query parameters, request body
templating, and response field selection.

To connect to an upstream REST API, you need to define your upstream `base_url`

, `timeout`

, and `auth`

type.

```
endpoints:
  - name: gold-api
    upstream:
      base_url: "https://api.gold-api.com"
      timeout: 10s
      auth:
        type: "none"
```

The base_url should be defined *without* a trailing slash `/`

.

Auth types supported are:

`bearer`

```
      auth:
        type: bearer                      # bearer | header | query | none
        env: ORDERS_API_KEY               # secret ALWAYS comes from this env var, never literal in file
```

`header`

```
      auth:
        type: header
        header: X-API-Key
        type: "none"
        env: ORDERS_API_KEY               # secret ALWAYS comes from this env var, never literal in file
```

`none`

```
      auth:
        type: "none"
```

For any key defined in `env`

it must be set in environment variables before starting the server. In the preceeding examples `ORDERS_API_KEY`

must be set

Tools are the MCP tools to create on the MCP server. Each tool has `name`

, `description`

, and `parameters`

```
    tools:
      - name: createInvoice
        description: "Create a invoice for a customer."
        parameters:
          type: object
          required: [customerId]
          properties:
            customerId:
              type: string
              description: "Customer to bill."
```

Whenever a toolcall is made to the attached tool, a coresponding http request is made to the upstream.

The `http`

object controls what how the HTTP request upstream is made. The usual parameters for a HTTP request including `method`

, `path`

and `body`

```
    tools:
      - name: getPrice
        description: "Fetch gold price"
        http:
          method: GET
          path: "/price/XAU"
```

A JSON request body can also be sent on any HTTP method by adding the `body`

```
        http:
          method: POST
          path: "/v1/invoices"
          body: # literal JSON template of exactly what to POST
            customerId: "123"
```

Suppose your tool has properties `customerId`

and `currency`

but you want to reference those when you make a http POST method upstream. You can reference them using `{parameter}`

syntax.

```
    tools:
      - name: createInvoice
        description: "Create a new draft invoice for a customer."
        parameters:
          type: object
          required: [customerId]
          properties:
            customerId:
              type: string
              description: "Customer to bill."
            currency:
              type: string
              description: "ISO-4217 currency code."
              default: "USD"
        http:
          method: POST
          path: "/v1/invoices"
          body:                            # literal JSON template of exactly what to POST
            customerId: "{customerId}"      # "{name}" -> substituted with that parameter's runtime value
            currency: "{currency}"
```

Sometimes you want to want to use parameters as query strings on the upstream request. To do this use `in`

on the properties.
For example the following config would result in a `HTTP GET`

to `/v1/invoices?customerId={customerId}¤cy={currency}`

```
          properties:
            customerId:
              type: string
              in: query
              description: "Customer to bill."
            currency:
              type: string
              in: query
              description: "ISO-4217 currency code."
        http:
          method: POST
          path: "/v1/invoices"
```

This also works in paths

```
        http:
          method: POST
          path: "/v1/invoices/{customerId}"
```

Suppose our API call to `https://api.gold-api.com/price/XAU`

returns JSON response

```
{
    "currency": "USD",
    "currencySymbol": "$",
    "exchangeRate": 1.0,
    "name": "Gold",
    "price": 4394.299805,
    "symbol": "XAU",
    "updatedAt": "2026-08-17T12:23:02Z",
    "updatedAtReadable": "a few seconds ago"
}
```

In many MCP cases it is preferred to use structured but compact text over raw JSON dumps. For something like this, a lightly formatted markdown table or bullet list is often easier for the model to reason over than raw JSON and cheaper in tokens.

To achieve this we can use the `http.response.select`

object to `JQ`

style select only the fields we need

```
http:
  method: GET
  path: "/price/XAU"
  response:
    select:
      currency: "{currency}"
      price: "{price}"
```

Here we use `"{currency}"`

variables to return fields from the JSON response which will return

```
currency: USD
price: 4394.299805
```

A tool call result in MCP follows this structure:

```
{
  "content": [
    {
      "type": "text",
      "text": "
        currency: USD
        price: 4394.299805
      "
    }
  ],
  "isError": false
}
```

Other available response formatting functions:

`{path.to.field}`

— plain field extraction`{arrayField[].path}`

— map a sub-path over every element of an array`{{literal}}`

— escape hatch for a literal string that looks like a path (e.g. {{id}} → the string "{id}")`{truncate(path, maxLen)}`

— truncate a string (or every string in an array-valued path) to maxLen

`mcp-proxy`

can be protected with an API key and setting `MCP_PROXY_AUTH_MODE`

and `MCP_PROXY_BEARER_TOKEN`

```
export MCP_PROXY_AUTH_MODE=bearer
openssl rand -base64 32
export MCP_PROXY_BEARER_TOKEN=****
```

To connect to `mcp-proxy`

you will need to pass header

```
Authorization: Bearer ***
go build ./...
go vet ./...
go test ./...
```


