#!/usr/bin/env python3 # Connect CrewAI to Terminal's remote MCP server. # The API key goes in the Authorization header, never in the URL. # MCPServerAdapter lists the tools. The question uses mcps with MCPServerHTTP. # This file does not confirm a proposal. import argparse 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 read_only(*args): tool = args[-1] if args else "" return is_read_tool(tool_name(tool)) def print_settings(key, url): print("framework: crewai") 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 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) def main(): parser = argparse.ArgumentParser( description="Read today's Terminal alerts through CrewAI." ) 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) run(key, url) if __name__ == "__main__": main()