telnyx-mcp-server-fastmcp 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. telnyx_mcp_server/__init__.py +0 -0
  2. telnyx_mcp_server/__main__.py +23 -0
  3. telnyx_mcp_server/config.py +148 -0
  4. telnyx_mcp_server/mcp.py +148 -0
  5. telnyx_mcp_server/server.py +497 -0
  6. telnyx_mcp_server/telnyx/__init__.py +1 -0
  7. telnyx_mcp_server/telnyx/client.py +363 -0
  8. telnyx_mcp_server/telnyx/services/__init__.py +0 -0
  9. telnyx_mcp_server/telnyx/services/assistants.py +155 -0
  10. telnyx_mcp_server/telnyx/services/call_control.py +217 -0
  11. telnyx_mcp_server/telnyx/services/cloud_storage.py +289 -0
  12. telnyx_mcp_server/telnyx/services/connections.py +92 -0
  13. telnyx_mcp_server/telnyx/services/embeddings.py +52 -0
  14. telnyx_mcp_server/telnyx/services/messaging.py +93 -0
  15. telnyx_mcp_server/telnyx/services/messaging_profiles.py +196 -0
  16. telnyx_mcp_server/telnyx/services/numbers.py +193 -0
  17. telnyx_mcp_server/telnyx/services/secrets.py +74 -0
  18. telnyx_mcp_server/tools/__init__.py +126 -0
  19. telnyx_mcp_server/tools/assistants.py +313 -0
  20. telnyx_mcp_server/tools/call_control.py +242 -0
  21. telnyx_mcp_server/tools/cloud_storage.py +183 -0
  22. telnyx_mcp_server/tools/connections.py +78 -0
  23. telnyx_mcp_server/tools/embeddings.py +80 -0
  24. telnyx_mcp_server/tools/messaging.py +57 -0
  25. telnyx_mcp_server/tools/messaging_profiles.py +123 -0
  26. telnyx_mcp_server/tools/phone_numbers.py +161 -0
  27. telnyx_mcp_server/tools/secrets.py +75 -0
  28. telnyx_mcp_server/tools/sms_conversations.py +455 -0
  29. telnyx_mcp_server/tools/webhooks.py +111 -0
  30. telnyx_mcp_server/utils/__init__.py +0 -0
  31. telnyx_mcp_server/utils/error_handler.py +30 -0
  32. telnyx_mcp_server/utils/logger.py +32 -0
  33. telnyx_mcp_server/utils/service.py +33 -0
  34. telnyx_mcp_server/webhook/__init__.py +25 -0
  35. telnyx_mcp_server/webhook/handler.py +596 -0
  36. telnyx_mcp_server/webhook/server.py +369 -0
  37. telnyx_mcp_server_fastmcp-0.1.3.dist-info/METADATA +430 -0
  38. telnyx_mcp_server_fastmcp-0.1.3.dist-info/RECORD +40 -0
  39. telnyx_mcp_server_fastmcp-0.1.3.dist-info/WHEEL +4 -0
  40. telnyx_mcp_server_fastmcp-0.1.3.dist-info/entry_points.txt +3 -0
File without changes
@@ -0,0 +1,23 @@
1
+ """Main entry point for the Telnyx MCP server."""
2
+
3
+ import logging
4
+ import os
5
+ import sys
6
+
7
+ # Set up logging before importing anything else
8
+ logging.basicConfig(
9
+ level=logging.INFO,
10
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
11
+ )
12
+ logger = logging.getLogger("telnyx-mcp")
13
+
14
+ # Check for API key
15
+ if not os.getenv("TELNYX_API_KEY"):
16
+ logger.critical("TELNYX_API_KEY environment variable not set")
17
+ sys.exit(1)
18
+
19
+ # Import server only after checking for API key
20
+ from .server import run_server
21
+
22
+ if __name__ == "__main__":
23
+ run_server()
@@ -0,0 +1,148 @@
1
+ """Configuration management for the Telnyx MCP server."""
2
+
3
+ import os
4
+ import random
5
+ import socket
6
+ from typing import Literal, Optional
7
+
8
+ from dotenv import load_dotenv
9
+ from pydantic import Field
10
+ from pydantic_settings import BaseSettings, SettingsConfigDict
11
+
12
+ # Load environment variables from .env file
13
+ load_dotenv()
14
+
15
+
16
+ # Generate a random port number in the dynamic/private port range
17
+ def get_random_high_port() -> int:
18
+ """
19
+ Generate a random port number in the higher end of the dynamic/private port range.
20
+
21
+ Returns:
22
+ int: A random port number between 50000 and 65000
23
+ """
24
+ return random.randint(50000, 65000)
25
+
26
+
27
+ def is_unix_socket_supported() -> bool:
28
+ """
29
+ Check if the system supports Unix domain sockets.
30
+
31
+ Returns:
32
+ bool: True if Unix domain sockets are supported, False otherwise
33
+ """
34
+ # On Unix-like systems, we can use os.name to check
35
+ if os.name != "posix":
36
+ return False
37
+
38
+ # Further check by trying to create a Unix domain socket
39
+ try:
40
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
41
+ # Clean up the socket
42
+ sock.close()
43
+ return True
44
+ except (AttributeError, OSError):
45
+ return False
46
+
47
+
48
+ # Determine API base URL based on environment
49
+ def get_api_base_url() -> str:
50
+ """
51
+ Return the Telnyx API base URL.
52
+
53
+ Returns:
54
+ str: The Telnyx API base URL
55
+ - Value of TELNYX_API_BASE environment variable if set
56
+ - https://api.telnyx.com/v2 otherwise
57
+ """
58
+ telnyx_api_base = os.getenv("TELNYX_API_BASE")
59
+ if telnyx_api_base:
60
+ return telnyx_api_base
61
+ return "https://api.telnyx.com/v2"
62
+
63
+
64
+ class Settings(BaseSettings):
65
+ """Server settings."""
66
+
67
+ # Telnyx API settings
68
+ telnyx_api_key: str = Field(
69
+ default=os.getenv("TELNYX_API_KEY", ""),
70
+ description="Telnyx API key for authentication",
71
+ )
72
+ telnyx_api_base_url: str = Field(
73
+ default=get_api_base_url(),
74
+ description="Base URL for Telnyx API",
75
+ )
76
+
77
+ # Server settings
78
+ host: str = Field(
79
+ default="0.0.0.0", description="Host to bind the server to"
80
+ )
81
+ port: int = Field(default=8000, description="Port to bind the server to")
82
+ debug: bool = Field(default=False, description="Enable debug mode")
83
+ log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = (
84
+ Field(default="INFO", description="Logging level")
85
+ )
86
+
87
+ # Webhook settings
88
+ webhook_enabled: bool = Field(
89
+ default=False, description="Enable webhook receiver"
90
+ )
91
+ webhook_port: int = Field(
92
+ default=get_random_high_port(),
93
+ description="Port to bind the webhook server to (random high port to avoid conflicts)",
94
+ )
95
+ webhook_path: str = Field(
96
+ default="/webhooks", description="Path for webhook endpoint"
97
+ )
98
+ webhook_max_body_size: int = Field(
99
+ default=1_048_576, # 1 MB
100
+ description="Maximum webhook request body size in bytes",
101
+ )
102
+ use_unix_socket: bool = Field(
103
+ default=is_unix_socket_supported(),
104
+ description="Use Unix domain socket instead of TCP/IP port (Unix-like systems only)",
105
+ )
106
+ socket_dir: Optional[str] = Field(
107
+ default=None,
108
+ description="Directory for Unix domain socket (default: system temp directory)",
109
+ )
110
+
111
+ # Ngrok settings
112
+ ngrok_enabled: bool = Field(
113
+ default=bool(os.getenv("NGROK_AUTHTOKEN", ""))
114
+ or bool(os.getenv("NGROK_URL", "")),
115
+ description="Enable ngrok tunnel for webhooks",
116
+ )
117
+ ngrok_authtoken: Optional[str] = Field(
118
+ default=os.getenv("NGROK_AUTHTOKEN", None),
119
+ description="Ngrok authentication token",
120
+ )
121
+
122
+ ngrok_url: Optional[str] = Field(
123
+ default=os.getenv("NGROK_URL", None),
124
+ description="NGROK custom domain - Experimental - TLS errors are possible.",
125
+ )
126
+
127
+ # MCP settings
128
+ warn_on_duplicate_resources: bool = Field(
129
+ default=True, description="Warn on duplicate resources"
130
+ )
131
+ warn_on_duplicate_tools: bool = Field(
132
+ default=True, description="Warn on duplicate tools"
133
+ )
134
+ warn_on_duplicate_prompts: bool = Field(
135
+ default=True, description="Warn on duplicate prompts"
136
+ )
137
+
138
+ model_config = SettingsConfigDict(
139
+ env_file=".env",
140
+ env_file_encoding="utf-8",
141
+ env_nested_delimiter="__",
142
+ extra="ignore",
143
+ validate_default=True,
144
+ )
145
+
146
+
147
+ # Create a global settings instance
148
+ settings = Settings()
@@ -0,0 +1,148 @@
1
+ """Simple MCP server using FastMCP framework with STDIO transport."""
2
+
3
+ import os
4
+ from typing import ( # Added Sequence
5
+ Any,
6
+ Dict,
7
+ List,
8
+ Optional,
9
+ Sequence,
10
+ )
11
+
12
+ from dotenv import load_dotenv
13
+ from fastmcp import FastMCP
14
+
15
+ # MCPTool is defined in mcp.types, but often exposed via fastmcp or mcp.server
16
+ # For clarity, let's try importing directly if fastmcp doesn't re-export it well.
17
+ try:
18
+ from fastmcp import MCPTool
19
+ except ImportError:
20
+ from mcp.types import (
21
+ Tool as MCPTool, # Fallback if not in fastmcp directly
22
+ )
23
+
24
+ from mcp.types import EmbeddedResource, ImageContent, TextContent
25
+
26
+ from .telnyx.client import TelnyxClient # Assuming this path is correct
27
+ from .utils.logger import get_logger # Assuming this path is correct
28
+
29
+ logger = get_logger(__name__)
30
+
31
+ # Load environment variables
32
+ load_dotenv()
33
+
34
+ # Get API key from environment
35
+ api_key = os.getenv("TELNYX_API_KEY", "")
36
+ if not api_key:
37
+ logger.error("TELNYX_API_KEY environment variable must be set")
38
+ raise ValueError("TELNYX_API_KEY environment variable must be set")
39
+
40
+
41
+ class FilterableFastMCP(FastMCP):
42
+ """Extended FastMCP class that supports tool filtering."""
43
+
44
+ def __init__(self, *args, **kwargs):
45
+ # Call super().__init__() first. This will run FastMCP's _setup_handlers,
46
+ # which will register self.list_tools and self.call_tool (from this FilterableFastMCP class)
47
+ super().__init__(*args, **kwargs)
48
+
49
+ self._enabled_tools: Optional[List[str]] = None
50
+ self._excluded_tools: List[str] = []
51
+ # Note: The original _original_list_tools_handler and _filtered_list_tools etc. are removed
52
+ # as the filtering is now done by overriding list_tools and call_tool directly.
53
+
54
+ def set_enabled_tools(self, tool_names: List[str]) -> None:
55
+ """
56
+ Set specific tools to enable. All other tools will be disabled.
57
+
58
+ Args:
59
+ tool_names: List of tool names to enable
60
+ """
61
+ self._enabled_tools = tool_names
62
+ logger.info(
63
+ f"Tool filtering enabled. Available tools: {', '.join(tool_names)}"
64
+ )
65
+
66
+ def set_excluded_tools(self, tool_names: List[str]) -> None:
67
+ """
68
+ Set specific tools to exclude. All other tools will remain enabled.
69
+
70
+ Args:
71
+ tool_names: List of tool names to exclude
72
+ """
73
+ self._excluded_tools = tool_names
74
+ logger.info(
75
+ f"Tool exclusion enabled. Excluded tools: {', '.join(tool_names)}"
76
+ )
77
+
78
+ async def list_tools(
79
+ self,
80
+ ) -> list[MCPTool]: # Matches signature from FastMCP
81
+ """Filter the list of tools based on enabled/excluded settings."""
82
+ all_mcp_tools = (
83
+ await super().list_tools()
84
+ ) # Get all tools as defined by FastMCP
85
+
86
+ # If no filtering is configured, return all tools
87
+ if self._enabled_tools is None and not self._excluded_tools:
88
+ return all_mcp_tools
89
+
90
+ filtered_mcp_tools = []
91
+ for tool_spec in all_mcp_tools:
92
+ # MCPTool has a 'name' attribute according to MCP spec and fastmcp usage
93
+ tool_name = tool_spec.name
94
+
95
+ # Check if tool should be included
96
+ if (
97
+ self._enabled_tools is not None
98
+ and tool_name not in self._enabled_tools
99
+ ):
100
+ logger.debug(
101
+ f"Filtering out tool: {tool_name} (not in enabled list)"
102
+ )
103
+ continue
104
+
105
+ # Check if tool should be excluded
106
+ if tool_name in self._excluded_tools:
107
+ logger.debug(
108
+ f"Filtering out tool: {tool_name} (in excluded list)"
109
+ )
110
+ continue
111
+
112
+ filtered_mcp_tools.append(tool_spec)
113
+
114
+ # This logging can be verbose if many tools, consider conditional logging or removing
115
+ # logger.info(f"Filtered tools from {len(all_mcp_tools)} to {len(filtered_mcp_tools)}")
116
+ return filtered_mcp_tools
117
+
118
+ async def call_tool(
119
+ self,
120
+ name: str,
121
+ arguments: Dict[str, Any], # 'name' instead of 'key' to match FastMCP
122
+ ) -> Sequence[
123
+ TextContent | ImageContent | EmbeddedResource
124
+ ]: # Matches signature
125
+ """Filter tool calls based on enabled/excluded settings."""
126
+ # Check if tool is allowed
127
+ if self._enabled_tools is not None and name not in self._enabled_tools:
128
+ logger.warning(f"Attempted to call disabled tool: '{name}'")
129
+ # Consider raising a specific MCP error type if available/appropriate
130
+ raise ValueError(f"Tool '{name}' is not enabled")
131
+
132
+ if name in self._excluded_tools:
133
+ logger.warning(f"Attempted to call excluded tool: '{name}'")
134
+ raise ValueError(f"Tool '{name}' is excluded")
135
+
136
+ try:
137
+ # Call the original FastMCP behavior to execute the tool
138
+ return await super().call_tool(name, arguments)
139
+ except Exception as e:
140
+ logger.error(f"Error calling tool '{name}': {str(e)}")
141
+ raise
142
+
143
+
144
+ # Create a single shared MCP instance with filtering support
145
+ mcp = FilterableFastMCP("Telnyx MCP")
146
+
147
+ # Initialize Telnyx client with API key from environment
148
+ telnyx_client = TelnyxClient(api_key=api_key)