Prerequisites
pip install langchain-mcp-adapters langgraph langchain-anthropic
Get a Flatland API key at /install — no card required (50 answers/month free). Your key starts with fl_live_.
Connect to Flatland
Pass Flatland's endpoint and your API key to MultiServerMCPClient. The client queries the server and converts each MCP tool into a LangChain StructuredTool:
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
"flatland": {
"transport": "streamable_http",
"url": "https://api.flatlandfi.com/mcp",
"headers": {"X-API-Key": "fl_live_YOUR_KEY_HERE"},
}
})
tools = await client.get_tools()
# flatland_init, flatland_create_model, flatland_bulk_add,
# flatland_compile, flatland_create_scenario, flatland_diff_scenarios,
# flatland_sensitivity, flatland_save_model, flatland_load_model, ...X-API-Key — not Authorization: Bearer. A 401 almost always means the wrong header name.First agent: SaaS P&L
This agent takes a business description and returns a compiled, typed P&L with assertions and a sensitivity ranking. It follows the standard Flatland session pattern: flatland_init → flatland_create_model → flatland_bulk_add → flatland_compile → flatland_sensitivity.
import asyncio
from langchain_anthropic import ChatAnthropic
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
async def build_model(description: str):
client = MultiServerMCPClient({
"flatland": {
"transport": "streamable_http",
"url": "https://api.flatlandfi.com/mcp",
"headers": {"X-API-Key": "fl_live_YOUR_KEY_HERE"},
}
})
tools = await client.get_tools()
agent = create_react_agent(
ChatAnthropic(model="claude-opus-4-5"),
tools,
)
return await agent.ainvoke({
"messages": [{
"role": "user",
"content": f"""
Call flatland_init first.
Build a financial model for this business:
{description}
1. flatland_create_model
2. flatland_bulk_add — all typed drivers, with
assertions on key metrics
3. flatland_compile
4. flatland_sensitivity on the primary output driver
5. Return compiled results and sensitivity ranking
""",
}]
})
result = asyncio.run(build_model(
"SaaS. $18K MRR, 12% monthly growth, 6% monthly churn. "
"$900 CAC, 18-month payback target. 4 engineers at $160K/yr. "
"Build a 12-month P&L with unit economics."
))The agent calls all five tools in sequence and returns compiled output: typed driver values, pass/fail assertion results, and a tornado ranking of which assumptions move your primary KPI most.
Scenario analysis
After building a base model, add a scenario to answer “what if?” questions. Only the changed assumptions differ from the base — the rest of the graph inherits unchanged.
async def run_scenario(base: str, question: str):
client = MultiServerMCPClient({
"flatland": {
"transport": "streamable_http",
"url": "https://api.flatlandfi.com/mcp",
"headers": {"X-API-Key": "fl_live_YOUR_KEY_HERE"},
}
})
tools = await client.get_tools()
agent = create_react_agent(
ChatAnthropic(model="claude-opus-4-5"),
tools,
)
return await agent.ainvoke({
"messages": [{
"role": "user",
"content": f"""
flatland_init, then build this base model: {base}
Then answer: {question}
1. flatland_create_model + flatland_bulk_add + flatland_compile
2. flatland_create_scenario — override only the
assumptions that change to answer the question
3. flatland_compile_scenario
4. flatland_diff_scenarios — return which drivers
changed and by how much each output moved
""",
}]
})
result = asyncio.run(run_scenario(
base="E-commerce. $50K/mo ad spend, 2.8% CVR, $85 AOV, 28% repeat rate.",
question="What happens if CVR drops to 1.4% and we cut ad spend 30%?",
))flatland_diff_scenariosreturns attribution: which changed driver caused which output delta, traced through the full dependency graph. Your agent can explain the “why” behind every number.
Persistent models across sessions
Flatland saves models server-side, scoped to your API key, in ~/.flatland/models/ on the Flatland server. Within the lifetime of a server instance you can reload a previously built model by name instead of rebuilding from scratch — but durable cross-deploy storage is not yet available, so treat saved models as a session-spanning cache, not a system of record, and keep the prompt that built them:
# Session 1 — build and save
await agent.ainvoke({"messages": [{"role": "user", "content":
"flatland_init, build a headcount model for a 12-person eng team "
"with loaded costs and runway assertion, then "
"flatland_save_model name='headcount-q3'"
}]})
# Session 2 — load and update
await agent.ainvoke({"messages": [{"role": "user", "content":
"flatland_init, flatland_load_model name='headcount-q3', "
"add 2 engineers at $175K/yr, recompile, "
"run sensitivity on total_headcount_cost"
}]})Metered tools
flatland_compileflatland_compile_scenarioflatland_sensitivityflatland_initflatland_create_modelflatland_bulk_addflatland_create_scenarioflatland_diff_scenariosflatland_save_modelflatland_load_modelflatland_get_graphflatland_trace_upstreamMetered tools each count as one “answer.” A sensitivity sweep across 50 drivers is one answer, not 50. Everything else — construction, inspection, saving, loading — is always free.
50 free answers per calendar month. Beyond that, $0.10 per answer with a default $50/month cap. Check current usage: GET api.flatlandfi.com/api/usage with your API key. Full detail at /pricing.


