#!/usr/bin/env python3 # Connect LangChain to Terminal's remote MCP server. # The API key goes in the Authorization header, never in the URL. # Uses MultiServerMCPClient (headers on the connection). See README.md for the # newer langchain.mcp.MCPAdapter shape. 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 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) def print_settings(key, url): print("framework: langchain") 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)}") def message_text(message): content = getattr(message, "content", message) if isinstance(content, str): return content if isinstance(content, list): parts = [] for block in content: if isinstance(block, str): parts.append(block) elif isinstance(block, dict): parts.append(str(block.get("text") or block.get("content") or "")) else: text = getattr(block, "text", None) parts.append(text if isinstance(text, str) else str(block)) return "\n".join(part for part in parts if part) return str(content) 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) def main(): parser = argparse.ArgumentParser( description="Read today's Terminal alerts through LangChain." ) 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()