Agent frameworks
An agent you build with the OpenAI Agents SDK, LangChain, or CrewAI can call Terminal's MCP server. MCP (Model Context Protocol) lets software call tools on a server you choose. Read MCP for the tools and what they cost.
Address and key
The address is https://terminal.tokensmarketcap.com/mcp. Each framework sends your API key on the Authorization: Bearer header. Create a key at API keys. Each tool call counts as one API call for the key. Read Authentication for scopes and caps.
The programs below read the key from TERMINAL_API_KEY. They read the address from TERMINAL_MCP_URL, and use the address above when it is unset. Put the key in TERMINAL_API_KEY only. A program refuses an address that carries a query, a fragment, or a user name. Do not commit a key, do not log it, and do not put it in a URL.
Each program lists the tool names, then asks one question that only reads: "What did my alerts record today?" The key needs the read scope for that. The programs leave out the tools that propose or confirm a change, and the two tools that can spend credits. None of them calls confirm_proposal.
The three programs share these settings:
DEFAULT_URL = "https://terminal.tokensmarketcap.com/mcp"
QUESTION = "What did my alerts record today?"
BLOCKED_TOOLS = {
"propose_rule",
"propose_edit",
"propose_state",
"propose_quiet_hours",
"confirm_proposal",
"cancel_proposal",
"preview_rule",
"explain_move",
}
INSTRUCTIONS = (
"You read Terminal's alert record for the person. "
"Answer from the tools. Use list_recent_alerts with since set to today when that tool is listed. "
"Describe what was recorded. Quote a price with its source and its time. "
"Treat null as unavailable. Do not recommend a trade, and do not estimate a future price. "
"Do not call a propose tool, confirm_proposal, or cancel_proposal."
)
They name tools with this helper:
def tool_name(tool):
if isinstance(tool, str):
return tool
if isinstance(tool, dict):
return str(tool.get("name") or "")
return str(getattr(tool, "name", "") or "")
Each program has a --dry-run flag. It prints the address and a masked key, then exits. It does not load the framework, so you can run it before you install one. The question uses the framework's own model key, usually OPENAI_API_KEY. --dry-run does not need it.
OpenAI Agents SDK
Download openai_agents_example.py. It uses MCPServerStreamableHttp and sends the key in params["headers"]. The blocks below are parts of that file.
pip install openai-agents
export TERMINAL_API_KEY="your API key"
export OPENAI_API_KEY="the key the SDK uses for its model"
python3 openai_agents_example.py --dry-run
python3 openai_agents_example.py
It prints the full tool list first. Then it blocks the change tools and the tools that spend credits, and asks the question:
async def run(key, url):
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp, create_static_tool_filter
from agents.model_settings import ModelSettings
async with MCPServerStreamableHttp(
name="Terminal",
params={
"url": url,
"headers": {"Authorization": f"Bearer {key}"},
"timeout": 10,
},
cache_tools_list=False,
max_retry_attempts=3,
) as server:
tools = await server.list_tools()
print("tools:")
for tool in tools:
print(tool_name(tool))
# Applied after the print, so the list above is the full catalog and the
# question cannot call a change tool. A fresh list_tools applies this block.
server.tool_filter = create_static_tool_filter(
blocked_tool_names=sorted(BLOCKED_TOOLS)
)
assistant = Agent(
name="Alert reader",
instructions=INSTRUCTIONS,
mcp_servers=[server],
model_settings=ModelSettings(tool_choice="required"),
)
result = await Runner.run(assistant, QUESTION)
print(result.final_output)
LangChain
Download langchain_example.py. It uses MultiServerMCPClient and sends the key in the connection's headers. The blocks below are parts of that file. The transport value http is that package's streamable HTTP transport.
pip install "langchain-mcp-adapters" "langchain[openai]"
export TERMINAL_API_KEY="your API key"
python3 langchain_example.py --dry-run
python3 langchain_example.py
It keeps the tools that only read:
def is_read_tool(name):
if not name or name.startswith("propose_") or name in BLOCKED_TOOLS:
return False
if "_propose_" in name:
return False
return not any(name.endswith("_" + blocked) for blocked in BLOCKED_TOOLS)
Then it asks the question with those tools. The model is TERMINAL_EXAMPLE_MODEL when you set it, and openai:gpt-4.1 otherwise.
async def run(key, url):
from langchain.agents import create_agent
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
"terminal": {
"transport": "http",
"url": url,
"headers": {"Authorization": f"Bearer {key}"},
},
})
tools = await client.get_tools()
print("tools:")
for tool in tools:
print(tool_name(tool))
read_tools = [tool for tool in tools if is_read_tool(tool_name(tool))]
model = os.environ.get("TERMINAL_EXAMPLE_MODEL", "openai:gpt-4.1")
agent = create_agent(model=model, tools=read_tools, system_prompt=INSTRUCTIONS)
result = await agent.ainvoke({
"messages": [{"role": "user", "content": QUESTION}],
})
messages = result.get("messages") if isinstance(result, dict) else None
if messages:
print(message_text(messages[-1]))
else:
print(result)
LangChain's newer MCPAdapter takes the same key as Client(url, auth=key).
CrewAI
Download crewai_example.py. It lists the tools with MCPServerAdapter. The blocks below are parts of that file. It asks the question with the mcps field and MCPServerHTTP. Both send the key on the same Authorization header.
pip install "crewai-tools[mcp]" mcp
export TERMINAL_API_KEY="your API key"
python3 crewai_example.py --dry-run
python3 crewai_example.py
It keeps the tools that only read, with the same check as the LangChain program:
def read_only(*args):
tool = args[-1] if args else ""
return is_read_tool(tool_name(tool))
def run(key, url):
from crewai import Agent, Crew, Task
from crewai.mcp import MCPServerHTTP
from crewai_tools import MCPServerAdapter
headers = {"Authorization": f"Bearer {key}"}
server_params = {
"url": url,
"transport": "streamable-http",
"headers": headers,
}
with MCPServerAdapter(server_params) as tools:
print("tools:")
for tool in tools:
print(tool_name(tool))
reader = Agent(
role="Alert reader",
goal="Report what today's alert record says.",
backstory=INSTRUCTIONS,
mcps=[
MCPServerHTTP(
url=url,
headers=headers,
streamable=True,
tool_filter=read_only,
)
],
verbose=False,
)
task = Task(
description=QUESTION,
expected_output="What today's alerts recorded, or that none were recorded.",
agent=reader,
)
result = Crew(agents=[reader], tasks=[task], verbose=False).kickoff()
print(result)
Changes
A change goes through a propose tool, then confirm_proposal, and only after the person agrees. The key needs rules:write for those tools. If your software lets a model call them, show the person the proposal's summary and confirm only after they say yes. cancel_proposal discards a proposal. Read Changes you confirm.
All developer pages
Terminal reports market events for conditions you set. It does not tell you to buy, sell, or hold, and it does not place trades.