#!/usr/bin/env python3 # Connect the OpenAI Agents SDK to Terminal's remote MCP server. # The API key goes in the Authorization header, never in the URL. # Uses MCPServerStreamableHttp (headers on params). This file does not confirm a proposal. import argparse import asyncio import os import sys from urllib.parse import urlsplit, urlunsplit 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." ) def mask_key(key): if not key: return "(not set)" if len(key) <= 8: return "****" return f"{key[:4]}...{key[-4:]}" def display_url(url): parts = urlsplit(url) host = parts.hostname or "" if host and ":" in host: host = f"[{host}]" if parts.port: host = f"{host}:{parts.port}" return urlunsplit((parts.scheme, host, parts.path, "", "")) def url_carries_secret(url): parts = urlsplit(url) return bool(parts.username or parts.password or parts.query or parts.fragment) 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 "") def print_settings(key, url): print("framework: openai-agents") print(f"url: {display_url(url)}") if url_carries_secret(url): print("url_note: query, fragment, and userinfo omitted") print(f"authorization: Bearer {mask_key(key)}") 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) def main(): parser = argparse.ArgumentParser( description="Read today's Terminal alerts through the OpenAI Agents SDK." ) parser.add_argument( "--dry-run", action="store_true", help="Print the connection settings and exit.", ) args = parser.parse_args() key = os.environ.get("TERMINAL_API_KEY", "").strip() url = os.environ.get("TERMINAL_MCP_URL", "").strip() or DEFAULT_URL if args.dry_run: print_settings(key, url) return if not key: print("Set TERMINAL_API_KEY to your Terminal API key.", file=sys.stderr) sys.exit(1) if url_carries_secret(url): print( "Put the API key in TERMINAL_API_KEY. Do not put it in TERMINAL_MCP_URL.", file=sys.stderr, ) sys.exit(1) asyncio.run(run(key, url)) if __name__ == "__main__": main()