# MCP servers with the exchange

Use any model on the exchange with the tools of an MCP server. An MCP client lists the tools of a server, lets a model choose a tool, and runs it. The exchange accepts OpenAI tool definitions, so the client converts each MCP tool to that format and sends it with the request.

To give an agent the tools of the exchange itself, see the [Liquid MCP server](https://inference.ai.exchange/docs/connect-mcp-server.md).

## Before you start

- A workspace with a balance and a `spend` key. See [API keys](https://inference.ai.exchange/docs/keys.md).
- The key exported in your shell:

  ```bash
  export LIQUID_API_KEY=sk-liquid-...
  ```

## 1. Install the packages

```bash
# Python 3.10 or later, and Node.js for the example server
pip install mcp openai
```

The example server needs Node.js.

## 2. Run the tool loop

This client starts a local file server, sends its tools with each request, and runs every tool call that the model returns. It stops when the model answers without a tool call.

```python
import asyncio
import json
import os

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from openai import AsyncOpenAI

client = AsyncOpenAI(
    base_url="https://router.inference.ai.exchange/v1",
    api_key=os.environ["LIQUID_API_KEY"],
    default_headers={"X-Title": "MCP", "x-liquid-cap-usd": "0.05"},
)
server = StdioServerParameters(
    command="npx",
    args=["-y", "@modelcontextprotocol/server-filesystem@1.0.0", os.getcwd()],
)

def to_openai_tool(tool):
    return {
        "type": "function",
        "function": {
            "name": tool.name,
            "description": tool.description or "",
            "parameters": tool.input_schema,
        },
    }

async def main(question):
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = [to_openai_tool(t) for t in (await session.list_tools()).tools]
            messages = [{"role": "user", "content": question}]
            for _ in range(8):
                response = await client.chat.completions.create(
                    model="liquid.auto", messages=messages, tools=tools
                )
                reply = response.choices[0].message
                messages.append(reply.model_dump(exclude_none=True))
                if not reply.tool_calls:
                    print(reply.content)
                    return
                for call in reply.tool_calls:
                    result = await session.call_tool(
                        call.function.name, json.loads(call.function.arguments)
                    )
                    text = "\n".join(p.text for p in result.content if p.type == "text")
                    messages.append({"role": "tool", "tool_call_id": call.id, "content": text})

asyncio.run(main("List the files in this directory."))
```

## What it costs

Each request is a separate auction, and each has its own charge. The `x-liquid-cap-usd` header limits the charge of one request. See [Purchasing rules](https://inference.ai.exchange/docs/rules.md).

The loop sends the whole conversation with each request. The exchange charges for every input token. Keep tool results short.

## Change the model

Choose another model, router model or preset from `GET /v1/models`. The model must support tools. See [Market data](https://inference.ai.exchange/docs/market.md).

## Notes

- A remote MCP server needs `streamable_http_client` from `mcp.client.streamable_http` in place of `stdio_client`. The rest of the loop does not change.
- MCP 2.0 renamed `inputSchema` to `input_schema` on a tool. Code for MCP 1.x that reads `inputSchema` fails on 2.x.
