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.

Before you start

1. Install the packages

# 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.

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/[email protected]", 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.

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.

Notes