MCP Server on Edge Compute 167 Lines of Python Telnyx released a 167-line Python MCP server for Edge Compute that exposes four Telnyx APIs as AI agent tools, including SMS, number search, and LLM inference. The server runs on Telnyx's private network, eliminating public internet round trips and reducing latency for agent tool calls. The Model Context Protocol MCP https://modelcontextprotocol.io has become the standard way for AI agents to call external tools. Claude, Cursor, and a growing ecosystem of agent frameworks speak MCP natively, they discover tools via a tools/list endpoint and call them via tools/call . Any HTTP service that implements that contract becomes a tool provider the agent can use. This walkthrough deploys a working MCP server to Telnyx Edge Compute. The whole server is 167 lines of Python in function/func.py , uses Python's standard library for the HTTP layer, and exposes four Telnyx APIs send sms , search numbers , run inference , list phone numbers as MCP tools. There is no Express, no FastAPI, no SDK, just urllib , json , os , and an ASGI handler. The canonical code example lives in the Telnyx code examples repo: https://github.com/team-telnyx/telnyx-code-examples/tree/main/edge-mcp-server-deploy-python What This Example Builds A single ASGI function deployed to Telnyx Edge Compute that exposes four MCP tools: | Tool | Telnyx API | Purpose | |---|---|---| send sms | POST /v2/messages | Send an SMS message to an E.164 number | search numbers | GET /v2/available phone numbers | Search available phone numbers by country and area code | run inference | POST /v2/ai/chat/completions | Run LLM inference via Telnyx AI OpenAI-compatible | list phone numbers | GET /v2/phone numbers | List phone numbers on the account | The function exposes four HTTP endpoints: | Method | Path | Purpose | |---|---|---| GET , POST | /mcp/tools/list | Return the tool catalog | POST | /mcp/tools/call | Execute a tool by name with arguments | GET | /health | Health check tool count, API key presence | GET | / | Service info name, tool list, endpoint paths | Once deployed, an AI agent that supports MCP can be pointed at the deployed URL and immediately call Telnyx APIs as part of its reasoning loop. Why Edge Compute Edge Compute runs serverless functions co-located with the Telnyx private network, the same network that handles voice, messaging, and AI inference. For an MCP server that calls Telnyx APIs as tools, that means: No public internet round trips for the tool execution path. The function runs in the same network as the Telnyx API endpoints. Single API key surface. The Telnyx API key is injected as a function secret at deploy time, never sent over the wire by the AI agent. No cold-start container orchestration. Edge Compute is a function runtime, not a container host. The MCP server sits one network hop away from every Telnyx API it calls. That is the carrier-edge advantage for AI agents: the same private network that carries SMS traffic carries the tool calls. Products Used telnyx products: Edge Compute, Messaging, Numbers, AI Inference Telnyx Edge Compute runs the ASGI function. Messaging powers send sms . Numbers powers search numbers and list phone numbers . AI Inference powers run inference via the OpenAI-compatible chat completions endpoint. Architecture AI Agent Claude / Cursor / MCP client │ │ HTTPS ▼ ┌──────────────────────┐ │ Edge Compute │ function/func.py │ ASGI handler │ 167 lines, stdlib only │ │ │ GET /mcp/tools/list │ → returns TOOLS array │ POST /mcp/tools/call │ → execute tool name, args │ GET /health │ └──────────┬────────────┘ │ Bearer TELNYX API KEY │ injected as secret at deploy time ▼ https://api.telnyx.com/v2 ├── /messages Messaging ├── /available phone numbers Numbers ├── /phone numbers Numbers └── /ai/chat/completions AI Inference The agent only knows the deployed URL. It never sees the Telnyx API key. The key is injected by the Edge Compute runtime as the TELNYX API KEY environment variable when the function boots. The Code The full function is 167 lines in function/func.py . Three pieces matter. The tool catalog The TOOLS array is the contract the agent reads during tools/list . Each entry has a name , a human-readable description , and a JSON Schema inputSchema . The schema is what the agent uses to construct valid arguments: TOOLS = { "name": "send sms", "description": "Send an SMS message via Telnyx", "inputSchema": { "type": "object", "properties": { "to": {"type": "string", "description": "Destination phone number in E.164 format"}, "from number": {"type": "string", "description": "Your Telnyx phone number in E.164"}, "text": {"type": "string", "description": "Message body"}, }, "required": "to", "from number", "text" , }, }, { "name": "search numbers", "description": "Search for available phone numbers to purchase", "inputSchema": { "type": "object", "properties": { "country code": {"type": "string", "description": "ISO 3166-1 alpha-2 country code", "default": "US"}, "area code": {"type": "string", "description": "Area code to search in"}, "limit": {"type": "integer", "description": "Max results", "default": 5}, }, }, }, { "name": "run inference", "description": "Run LLM inference via Telnyx AI OpenAI-compatible ", "inputSchema": { "type": "object", "properties": { "prompt": {"type": "string", "description": "User prompt"}, "model": {"type": "string", "description": "Model name", "default": "moonshotai/Kimi-K2.6"}, "max tokens": {"type": "integer", "default": 256}, }, "required": "prompt" , }, }, { "name": "list phone numbers", "description": "List phone numbers on your Telnyx account", "inputSchema": { "type": "object", "properties": { "page size": {"type": "integer", "default": 10}, }, }, }, The agent reads this array, picks the right tool for the user's intent, fills in the arguments, and POSTs to /mcp/tools/call . The tool executor execute tool is a flat dispatcher. The MCP layer extracts name and arguments from the request body and hands them to this method, which translates them into Telnyx REST calls. Every tool hits a different endpoint with different body shapes: python def execute tool self, name, args : if name == "send sms": return self. telnyx request "POST", "/messages", { "from": args "from number" , "to": args "to" , "text": args "text" , } elif name == "search numbers": cc = args.get "country code", "US" limit = args.get "limit", 5 path = f"/available phone numbers?filter country code ={cc}&filter limit ={limit}" if args.get "area code" : path += f"&filter national destination code ={args 'area code' }" return self. telnyx request "GET", path elif name == "run inference": return self. telnyx request "POST", "/ai/chat/completions", { "model": args.get "model", "moonshotai/Kimi-K2.6" , "messages": {"role": "user", "content": args "prompt" } , "max tokens": args.get "max tokens", 256 , } elif name == "list phone numbers": size = args.get "page size", 10 return self. telnyx request "GET", f"/phone numbers?page size ={size}" return {"error": f"Unknown tool: {name}"} Adding a new tool means appending to TOOLS and adding one more branch here. The two stay in sync because both reference the same string names. The ASGI handler The whole HTTP surface is one method. Edge Compute calls handle scope, receive, send on every request, the ASGI contract. The method routes on path and method , reads the JSON body for tools/call , and returns a JSON response: python async def handle self, scope, receive, send : if scope.get "type" = HTTP SCOPE TYPE: await self. respond send, 400, {"error": "not http"} return path = scope.get "path", "/" method = scope.get "method", "GET" if path == "/mcp/tools/list" and method in "GET", "POST" : await self. respond send, 200, {"tools": TOOLS} return if path == "/mcp/tools/call" and method == "POST": body = await self. read body receive try: req data = json.loads body except json.JSONDecodeError: await self. respond send, 400, {"error": "invalid json"} return tool name = req data.get "params", {} .get "name", req data.get "name", "" tool args = req data.get "params", {} .get "arguments", req data.get "arguments", {} result = self. execute tool tool name, tool args await self. respond send, 200, { "content": {"type": "text", "text": json.dumps result, indent=2 } , "isError": "error" in result, } return if path == "/health" and method == "GET": await self. respond send, 200, { "status": "ok", "tools": len TOOLS , "has api key": bool self.api key , } return if path == "/" and method == "GET": await self. respond send, 200, { "name": "telnyx-mcp", "description": "Telnyx MCP Server, send SMS, search numbers, run inference", "tools": t "name" for t in TOOLS , "endpoints": {"list tools": "/mcp/tools/list", "call tool": "/mcp/tools/call", "health": "/health"}, } return await self. respond send, 404, {"error": "not found"} The handler accepts both params and a flat shape for the tool name and arguments, so it works with MCP clients that send either convention. Understanding MCP MCP is an HTTP contract. There are two methods any server must support: , return a JSON array of tool definitions. Each tool has a tools/list name , description , and JSON Schema describing its arguments. The agent reads this to know what it can call., accept a JSON body with tools/call name and arguments , execute the tool, and return the result as a content array with a type of text and a text field containing the JSON-stringified result. That's it. No streaming, no sessions, no authentication at the MCP layer, the transport is plain HTTPS with JSON bodies. Any HTTP client can be an MCP server. Any HTTP server exposing those two endpoints is an MCP server. The Telnyx implementation accepts both {"name": "send sms", "arguments": {...}} flat and {"params": {"name": "send sms", "arguments": {...}}} nested shapes, because different MCP clients use different conventions. Deploy It git clone https://github.com/team-telnyx/telnyx-code-examples.git cd telnyx-code-examples/edge-mcp-server-deploy-python telnyx-edge auth login telnyx-edge secrets add TELNYX API KEY