How to Build an MCP Client for FXMacroData
By FXMacroData Team
Published on June 10, 2026
This tutorial follows the shape of the official Build an MCP Client guide, but adapts it for a real remote server instead of a local demo process. By the end, you will have a Python client that connects to the production FXMacroData MCP endpoint, lists tools, and calls live macro data from USD inflation, the release calendar, and other FX workflows.
A minimal Python MCP client that connects to
https://fxmacrodata.com/mcp over Streamable HTTP, initializes a session, discovers tools, and calls them from code. Then you will see how to turn the same client into an LLM-powered research assistant.
Prerequisites
- Python 3.11 or newer.
- uv or
pipfor dependency management. - An FXMacroData API key if you want non-USD data, COT, or commodities. Public USD access works without a key.
- A basic understanding of async Python.
If you only want to prove the connection first, you can use the public MCP URL with no credentials. That is enough for recent USD indicator queries, FX rates, catalogue lookups, and session status. When you want broader data such as policy rate comparisons across currencies or COT positioning, append your API key as a query parameter or use OAuth.
Step 1. Create the project and install the MCP SDK
The official tutorial starts with a local client project. Do the same here:
mkdir fxmd-mcp-client
cd fxmd-mcp-client
uv init
uv add mcp python-dotenv
If you prefer pip, this is equivalent:
python -m venv .venv
source .venv/bin/activate
pip install mcp python-dotenv
The key change from the official demo is transport. The tutorial on modelcontextprotocol.io shows a client that launches a local server over stdio. FXMacroData is hosted remotely, so your client will use the Python SDK's Streamable HTTP transport instead.
Step 2. Store the server URL and optional API key
Create a .env file in the project root:
FXMD_MCP_URL=https://fxmacrodata.com/mcp
FXMD_API_KEY=
Leave FXMD_API_KEY empty if you only want public USD access. When you are ready for protected tools, set it and the client will connect with:
https://fxmacrodata.com/mcp?api_key=YOUR_API_KEY
This is the quickest path for a custom client. For production user-facing apps, OAuth is usually better because it avoids shipping a shared API key inside your application.
Step 3. Write a minimal remote MCP client
Create client.py and add the following code:
import argparse
import asyncio
import json
import os
from urllib.parse import urlencode
from dotenv import load_dotenv
from mcp import ClientSession, types
from mcp.client.streamable_http import streamable_http_client
load_dotenv()
def build_server_url() -> str:
base_url = os.getenv("FXMD_MCP_URL", "https://fxmacrodata.com/mcp")
api_key = os.getenv("FXMD_API_KEY", "").strip()
if not api_key or "api_key=" in base_url:
return base_url
separator = "&" if "?" in base_url else "?"
return f"{base_url}{separator}{urlencode({'api_key': api_key})}"
def extract_content(result: types.CallToolResult) -> str:
parts = []
if result.structuredContent:
parts.append(json.dumps(result.structuredContent, indent=2))
for item in result.content:
if isinstance(item, types.TextContent):
parts.append(item.text)
return "\n".join(parts).strip()
async def run() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("command", choices=["tools", "call"])
parser.add_argument("--tool", help="Tool name to call")
parser.add_argument(
"--args",
default="{}",
help='JSON object of tool arguments, for example: {"currency":"usd"}',
)
cli_args = parser.parse_args()
server_url = build_server_url()
async with streamable_http_client(server_url) as (
read_stream,
write_stream,
_,
):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()
if cli_args.command == "tools":
tools = await session.list_tools()
print(json.dumps([tool.model_dump() for tool in tools.tools], indent=2))
return
if not cli_args.tool:
raise SystemExit("--tool is required when command=call")
arguments = json.loads(cli_args.args)
result = await session.call_tool(cli_args.tool, arguments)
print(extract_content(result))
if __name__ == "__main__":
asyncio.run(run())
This does four important things:
- Loads the production FXMacroData MCP URL from the environment.
- Appends
?api_key=...only when you actually have one. - Uses
streamable_http_client()instead of the localstdio_client()transport from the basic tutorial. - Prints both structured JSON and plain-text tool output, which makes debugging much easier.
Step 4. Verify the connection and inspect the available tools
Start by listing tools:
uv run python client.py tools
You should see tools such as:
data_catalogueindicator_queryrelease_calendarforexmarket_sessions
If you are following the official MCP tutorial closely, this is the remote equivalent of the "make sure the client can discover tools" checkpoint.
Now call the catalogue for public USD coverage:
uv run python client.py call --tool data_catalogue --args "{\"currency\":\"usd\"}"
Then pull recent inflation data:
uv run python client.py call --tool indicator_query --args "{\"currency\":\"usd\",\"indicator\":\"inflation\",\"start_date\":\"2025-01-01\",\"end_date\":\"2026-01-01\"}"
And finally inspect the next USD events on the release calendar:
uv run python client.py call --tool release_calendar --args "{\"currency\":\"usd\"}"
At this point you have a real MCP client, not just a config file entry inside an editor. That matters if you want to embed the workflow into your own backend, notebook helper, or research UI.
Step 5. Add a simple research workflow
Once tool discovery works, the next useful move is composing a few calls. For example, you can compare the most recent Federal Reserve context against EUR/USD by querying policy rates and spot in sequence.
Add this helper inside client.py if you want a concrete pattern:
async def quick_macro_snapshot(session: ClientSession) -> None:
usd_rates = await session.call_tool(
"indicator_query",
{
"currency": "usd",
"indicator": "policy_rate",
"start_date": "2024-01-01",
"end_date": "2026-12-31",
},
)
eur_rates = await session.call_tool(
"indicator_query",
{
"currency": "eur",
"indicator": "policy_rate",
"start_date": "2024-01-01",
"end_date": "2026-12-31",
},
)
spot = await session.call_tool(
"forex",
{"base": "eur", "quote": "usd"},
)
print("USD policy rate:")
print(extract_content(usd_rates))
print("\nEUR policy rate:")
print(extract_content(eur_rates))
print("\nEUR/USD spot:")
print(extract_content(spot))
This is also the point where authentication matters. The public server is enough for USD-only discovery and short-range public data. Multi-currency comparisons like EUR versus USD typically need a paid API key or an OAuth token.
Step 6. Turn the same MCP client into an LLM tool layer
The official build-client guide usually ends with a chatbot loop. The same idea works here: connect to FXMacroData first, then hand the discovered tool schemas to your model so it can decide when to call them.
The exact LLM provider is up to you. The important pattern is:
- Open the MCP session.
- Call
list_tools(). - Convert those tools into your model provider's tool schema.
- When the model requests a tool call, execute it through
session.call_tool(). - Feed the tool result back to the model and continue the loop.
A compact sketch looks like this:
async def chat_loop(session: ClientSession, llm_client, user_prompt: str) -> str:
tools = await session.list_tools()
tool_specs = [
{
"type": "function",
"name": tool.name,
"description": tool.description or "",
"parameters": tool.inputSchema,
}
for tool in tools.tools
]
messages = [{"role": "user", "content": user_prompt}]
while True:
response = llm_client.responses.create(
model="gpt-4.1",
input=messages,
tools=tool_specs,
)
output = response.output[0]
if output.type == "message":
return output.content[0].text
if output.type == "function_call":
result = await session.call_tool(
output.name,
json.loads(output.arguments),
)
messages.append(output.model_dump())
messages.append(
{
"type": "function_call_output",
"call_id": output.call_id,
"output": extract_content(result),
}
)
The model name here is just an example. The real takeaway is architectural: FXMacroData stays the tool source, while your chosen model handles reasoning and language generation.
Step 7. Know when direct REST is still the better choice
MCP is ideal when you want discovery, tool schemas, and an agent loop. Direct REST is still better for deterministic scripts, cron jobs, and data pipelines.
For example, if you already know you only need one endpoint, a plain HTTP request is simpler:
curl "https://fxmacrodata.com/api/v1/announcements/usd/inflation?api_key=YOUR_API_KEY"
In practice, a good split is:
- REST for stable production pulls and scheduled jobs.
- MCP for exploratory research, chat interfaces, and agent-driven workflows.
If you are building an AI research assistant, you will often use both: MCP for the conversational tool layer, REST for the reproducible reporting and storage layer underneath.
Troubleshooting
- You can list tools but protected calls fail. Your server connection works; your API key or OAuth token does not have access to that dataset.
- The client fails before
initialize(). Double-check that you are pointing athttps://fxmacrodata.com/mcpand not a website page. - You only need a quick smoke test. Start with
data_catalogueforusd, then trymarket_sessions. - You want user-by-user auth. Replace the query-param API key shortcut with OAuth and store tokens per user session.
Wrapping up
You now have the same core shape as the official MCP client tutorial, but pointed at a live remote server that is useful for macro and FX work. The crucial adaptation is swapping local stdio transport for remote Streamable HTTP, then treating FXMacroData as the tool source for your client.
From here, the sensible next steps are to add OAuth, persist conversation state, and build one concrete workflow around your highest-value use case, whether that is pre-market briefing, macro event monitoring, or pair-level research around releases and spot reactions.