signalwire-agents 0.1.27__py3-none-any.whl → 0.1.28__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.
- signalwire_agents/__init__.py +1 -4
- signalwire_agents/cli/config.py +11 -1
- signalwire_agents/cli/simulation/data_overrides.py +6 -2
- signalwire_agents/cli/test_swaig.py +6 -0
- signalwire_agents/core/agent_base.py +1 -12
- signalwire_agents/core/mixins/state_mixin.py +1 -67
- signalwire_agents/core/mixins/tool_mixin.py +0 -65
- signalwire_agents/prefabs/concierge.py +0 -3
- signalwire_agents/prefabs/faq_bot.py +0 -3
- signalwire_agents/prefabs/info_gatherer.py +0 -3
- signalwire_agents/prefabs/receptionist.py +0 -3
- signalwire_agents/prefabs/survey.py +0 -3
- signalwire_agents/skills/mcp_gateway/README.md +230 -0
- signalwire_agents/skills/mcp_gateway/__init__.py +1 -0
- signalwire_agents/skills/mcp_gateway/skill.py +339 -0
- {signalwire_agents-0.1.27.dist-info → signalwire_agents-0.1.28.dist-info}/METADATA +1 -59
- {signalwire_agents-0.1.27.dist-info → signalwire_agents-0.1.28.dist-info}/RECORD +21 -21
- signalwire_agents/core/state/__init__.py +0 -17
- signalwire_agents/core/state/file_state_manager.py +0 -219
- signalwire_agents/core/state/state_manager.py +0 -101
- {signalwire_agents-0.1.27.dist-info → signalwire_agents-0.1.28.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.27.dist-info → signalwire_agents-0.1.28.dist-info}/entry_points.txt +0 -0
- {signalwire_agents-0.1.27.dist-info → signalwire_agents-0.1.28.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.27.dist-info → signalwire_agents-0.1.28.dist-info}/top_level.txt +0 -0
signalwire_agents/__init__.py
CHANGED
@@ -18,13 +18,12 @@ A package for building AI agents using SignalWire's AI and SWML capabilities.
|
|
18
18
|
from .core.logging_config import configure_logging
|
19
19
|
configure_logging()
|
20
20
|
|
21
|
-
__version__ = "0.1.
|
21
|
+
__version__ = "0.1.28"
|
22
22
|
|
23
23
|
# Import core classes for easier access
|
24
24
|
from .core.agent_base import AgentBase
|
25
25
|
from .core.contexts import ContextBuilder, Context, Step, create_simple_context
|
26
26
|
from .core.data_map import DataMap, create_simple_api_tool, create_expression_tool
|
27
|
-
from .core.state import StateManager, FileStateManager
|
28
27
|
from signalwire_agents.agent_server import AgentServer
|
29
28
|
from signalwire_agents.core.swml_service import SWMLService
|
30
29
|
from signalwire_agents.core.swml_builder import SWMLBuilder
|
@@ -68,8 +67,6 @@ __all__ = [
|
|
68
67
|
"AgentServer",
|
69
68
|
"SWMLService",
|
70
69
|
"SWMLBuilder",
|
71
|
-
"StateManager",
|
72
|
-
"FileStateManager",
|
73
70
|
"SwaigFunctionResult",
|
74
71
|
"SWAIGFunction",
|
75
72
|
"DataMap",
|
signalwire_agents/cli/config.py
CHANGED
@@ -41,12 +41,22 @@ ERROR_FUNCTION_NOT_FOUND = "Function '{function_name}' not found in agent"
|
|
41
41
|
ERROR_CGI_HOST_REQUIRED = "CGI simulation requires --cgi-host"
|
42
42
|
|
43
43
|
# Help messages
|
44
|
-
HELP_DESCRIPTION = "Test SWAIG functions and generate SWML documents for SignalWire AI agents
|
44
|
+
HELP_DESCRIPTION = """Test SWAIG functions and generate SWML documents for SignalWire AI agents
|
45
|
+
|
46
|
+
IMPORTANT: When using --exec, ALL options (like --call-id, --verbose, etc.) must come BEFORE --exec.
|
47
|
+
Everything after --exec <function_name> is treated as arguments to the function."""
|
48
|
+
|
45
49
|
HELP_EPILOG_SHORT = """
|
46
50
|
examples:
|
47
51
|
# Execute a function
|
48
52
|
%(prog)s agent.py --exec search --query "test" --limit 5
|
49
53
|
|
54
|
+
# Execute with persistent session (--call-id MUST come BEFORE --exec)
|
55
|
+
%(prog)s agent.py --call-id my-session --exec add_todo --text "Buy milk"
|
56
|
+
|
57
|
+
# WRONG: This won't work! --call-id is treated as a function argument
|
58
|
+
%(prog)s agent.py --exec add_todo --text "Buy milk" --call-id my-session
|
59
|
+
|
50
60
|
# Generate SWML
|
51
61
|
%(prog)s agent.py --dump-swml --raw | jq '.'
|
52
62
|
|
@@ -120,8 +120,12 @@ def apply_convenience_mappings(data: Dict[str, Any], args: argparse.Namespace) -
|
|
120
120
|
|
121
121
|
# Map high-level arguments to specific paths
|
122
122
|
if hasattr(args, 'call_id') and args.call_id:
|
123
|
-
|
124
|
-
|
123
|
+
# Set at root level for SWAIG functions
|
124
|
+
data["call_id"] = args.call_id
|
125
|
+
# Also set in call object if it exists
|
126
|
+
if "call" in data:
|
127
|
+
set_nested_value(data, "call.call_id", args.call_id)
|
128
|
+
set_nested_value(data, "call.tag", args.call_id) # tag often matches call_id
|
125
129
|
|
126
130
|
if hasattr(args, 'project_id') and args.project_id:
|
127
131
|
set_nested_value(data, "call.project_id", args.project_id)
|
@@ -727,6 +727,12 @@ def main():
|
|
727
727
|
# Default behavior - minimal data
|
728
728
|
post_data = generate_minimal_post_data(args.tool_name, function_args)
|
729
729
|
|
730
|
+
# Apply convenience mappings from CLI args (e.g., --call-id)
|
731
|
+
post_data = apply_convenience_mappings(post_data, args)
|
732
|
+
|
733
|
+
# Apply explicit overrides
|
734
|
+
post_data = apply_overrides(post_data, args.override, args.override_json)
|
735
|
+
|
730
736
|
if args.verbose:
|
731
737
|
print(f"Post data: {json.dumps(post_data, indent=2)}")
|
732
738
|
print("-" * 60)
|
@@ -49,7 +49,6 @@ from signalwire_agents.core.swaig_function import SWAIGFunction
|
|
49
49
|
from signalwire_agents.core.function_result import SwaigFunctionResult
|
50
50
|
from signalwire_agents.core.swml_renderer import SwmlRenderer
|
51
51
|
from signalwire_agents.core.security.session_manager import SessionManager
|
52
|
-
from signalwire_agents.core.state import StateManager, FileStateManager
|
53
52
|
from signalwire_agents.core.swml_service import SWMLService
|
54
53
|
from signalwire_agents.core.swml_handler import AIVerbHandler
|
55
54
|
from signalwire_agents.core.skill_manager import SkillManager
|
@@ -113,13 +112,11 @@ class AgentBase(
|
|
113
112
|
port: int = 3000,
|
114
113
|
basic_auth: Optional[Tuple[str, str]] = None,
|
115
114
|
use_pom: bool = True,
|
116
|
-
enable_state_tracking: bool = False,
|
117
115
|
token_expiry_secs: int = 3600,
|
118
116
|
auto_answer: bool = True,
|
119
117
|
record_call: bool = False,
|
120
118
|
record_format: str = "mp4",
|
121
119
|
record_stereo: bool = True,
|
122
|
-
state_manager: Optional[StateManager] = None,
|
123
120
|
default_webhook_url: Optional[str] = None,
|
124
121
|
agent_id: Optional[str] = None,
|
125
122
|
native_functions: Optional[List[str]] = None,
|
@@ -138,13 +135,11 @@ class AgentBase(
|
|
138
135
|
port: Port to bind the web server to
|
139
136
|
basic_auth: Optional (username, password) tuple for basic auth
|
140
137
|
use_pom: Whether to use POM for prompt building
|
141
|
-
enable_state_tracking: Whether to register startup_hook and hangup_hook SWAIG functions to track conversation state
|
142
138
|
token_expiry_secs: Seconds until tokens expire
|
143
139
|
auto_answer: Whether to automatically answer calls
|
144
140
|
record_call: Whether to record calls
|
145
141
|
record_format: Recording format
|
146
142
|
record_stereo: Whether to record in stereo
|
147
|
-
state_manager: Optional state manager for this agent
|
148
143
|
default_webhook_url: Optional default webhook URL for all SWAIG functions
|
149
144
|
agent_id: Optional unique ID for this agent, generated if not provided
|
150
145
|
native_functions: Optional list of native functions to include in the SWAIG object
|
@@ -207,7 +202,6 @@ class AgentBase(
|
|
207
202
|
|
208
203
|
# Initialize session manager
|
209
204
|
self._session_manager = SessionManager(token_expiry_secs=token_expiry_secs)
|
210
|
-
self._enable_state_tracking = enable_state_tracking
|
211
205
|
|
212
206
|
# URL override variables
|
213
207
|
self._web_hook_url_override = None
|
@@ -229,8 +223,6 @@ class AgentBase(
|
|
229
223
|
# Process declarative PROMPT_SECTIONS if defined in subclass
|
230
224
|
self._process_prompt_sections()
|
231
225
|
|
232
|
-
# Initialize state manager
|
233
|
-
self._state_manager = state_manager or FileStateManager()
|
234
226
|
|
235
227
|
# Process class-decorated tools (using @AgentBase.tool)
|
236
228
|
self._tool_registry.register_class_decorated_tools()
|
@@ -238,9 +230,6 @@ class AgentBase(
|
|
238
230
|
# Add native_functions parameter
|
239
231
|
self.native_functions = native_functions or []
|
240
232
|
|
241
|
-
# Register state tracking tools if enabled
|
242
|
-
if enable_state_tracking:
|
243
|
-
self._register_state_tracking_tools()
|
244
233
|
|
245
234
|
# Initialize new configuration containers
|
246
235
|
self._hints = []
|
@@ -581,7 +570,7 @@ class AgentBase(
|
|
581
570
|
post_prompt = agent_to_use.get_post_prompt()
|
582
571
|
|
583
572
|
# Generate a call ID if needed
|
584
|
-
if
|
573
|
+
if call_id is None:
|
585
574
|
call_id = agent_to_use._session_manager.create_session()
|
586
575
|
|
587
576
|
# Start with any SWAIG query params that were set
|
@@ -150,70 +150,4 @@ class StateMixin:
|
|
150
150
|
self.log.error("token_validation_error", error=str(e), function=function_name)
|
151
151
|
return False
|
152
152
|
|
153
|
-
# Note: set_dynamic_config_callback is implemented in WebMixin
|
154
|
-
|
155
|
-
def _register_state_tracking_tools(self):
|
156
|
-
"""
|
157
|
-
Register special tools for state tracking
|
158
|
-
|
159
|
-
This adds startup_hook and hangup_hook SWAIG functions that automatically
|
160
|
-
activate and deactivate the session when called. These are useful for
|
161
|
-
tracking call state and cleaning up resources when a call ends.
|
162
|
-
"""
|
163
|
-
# Register startup hook to activate session
|
164
|
-
self.define_tool(
|
165
|
-
name="startup_hook",
|
166
|
-
description="Called when a new conversation starts to initialize state",
|
167
|
-
parameters={},
|
168
|
-
handler=lambda args, raw_data: self._handle_startup_hook(args, raw_data),
|
169
|
-
secure=False # No auth needed for this system function
|
170
|
-
)
|
171
|
-
|
172
|
-
# Register hangup hook to end session
|
173
|
-
self.define_tool(
|
174
|
-
name="hangup_hook",
|
175
|
-
description="Called when conversation ends to clean up resources",
|
176
|
-
parameters={},
|
177
|
-
handler=lambda args, raw_data: self._handle_hangup_hook(args, raw_data),
|
178
|
-
secure=False # No auth needed for this system function
|
179
|
-
)
|
180
|
-
|
181
|
-
def _handle_startup_hook(self, args, raw_data):
|
182
|
-
"""
|
183
|
-
Handle the startup hook function call
|
184
|
-
|
185
|
-
Args:
|
186
|
-
args: Function arguments (empty for this hook)
|
187
|
-
raw_data: Raw request data containing call_id
|
188
|
-
|
189
|
-
Returns:
|
190
|
-
Success response
|
191
|
-
"""
|
192
|
-
call_id = raw_data.get("call_id") if raw_data else None
|
193
|
-
if call_id:
|
194
|
-
self.log.info("session_activated", call_id=call_id)
|
195
|
-
self._session_manager.activate_session(call_id)
|
196
|
-
return SwaigFunctionResult("Session activated")
|
197
|
-
else:
|
198
|
-
self.log.warning("session_activation_failed", error="No call_id provided")
|
199
|
-
return SwaigFunctionResult("Failed to activate session: No call_id provided")
|
200
|
-
|
201
|
-
def _handle_hangup_hook(self, args, raw_data):
|
202
|
-
"""
|
203
|
-
Handle the hangup hook function call
|
204
|
-
|
205
|
-
Args:
|
206
|
-
args: Function arguments (empty for this hook)
|
207
|
-
raw_data: Raw request data containing call_id
|
208
|
-
|
209
|
-
Returns:
|
210
|
-
Success response
|
211
|
-
"""
|
212
|
-
call_id = raw_data.get("call_id") if raw_data else None
|
213
|
-
if call_id:
|
214
|
-
self.log.info("session_ended", call_id=call_id)
|
215
|
-
self._session_manager.end_session(call_id)
|
216
|
-
return SwaigFunctionResult("Session ended")
|
217
|
-
else:
|
218
|
-
self.log.warning("session_end_failed", error="No call_id provided")
|
219
|
-
return SwaigFunctionResult("Failed to end session: No call_id provided")
|
153
|
+
# Note: set_dynamic_config_callback is implemented in WebMixin
|
@@ -161,71 +161,6 @@ class ToolMixin:
|
|
161
161
|
# If the handler raises an exception, return an error response
|
162
162
|
return {"response": f"Error executing function '{name}': {str(e)}"}
|
163
163
|
|
164
|
-
def _register_state_tracking_tools(self):
|
165
|
-
"""
|
166
|
-
Register special tools for state tracking
|
167
|
-
|
168
|
-
This adds startup_hook and hangup_hook SWAIG functions that automatically
|
169
|
-
activate and deactivate the session when called. These are useful for
|
170
|
-
tracking call state and cleaning up resources when a call ends.
|
171
|
-
"""
|
172
|
-
# Register startup hook to activate session
|
173
|
-
self.define_tool(
|
174
|
-
name="startup_hook",
|
175
|
-
description="Called when a new conversation starts to initialize state",
|
176
|
-
parameters={},
|
177
|
-
handler=lambda args, raw_data: self._handle_startup_hook(args, raw_data),
|
178
|
-
secure=False # No auth needed for this system function
|
179
|
-
)
|
180
|
-
|
181
|
-
# Register hangup hook to end session
|
182
|
-
self.define_tool(
|
183
|
-
name="hangup_hook",
|
184
|
-
description="Called when conversation ends to clean up resources",
|
185
|
-
parameters={},
|
186
|
-
handler=lambda args, raw_data: self._handle_hangup_hook(args, raw_data),
|
187
|
-
secure=False # No auth needed for this system function
|
188
|
-
)
|
189
|
-
|
190
|
-
def _handle_startup_hook(self, args, raw_data):
|
191
|
-
"""
|
192
|
-
Handle the startup hook function call
|
193
|
-
|
194
|
-
Args:
|
195
|
-
args: Function arguments (empty for this hook)
|
196
|
-
raw_data: Raw request data containing call_id
|
197
|
-
|
198
|
-
Returns:
|
199
|
-
Success response
|
200
|
-
"""
|
201
|
-
call_id = raw_data.get("call_id") if raw_data else None
|
202
|
-
if call_id:
|
203
|
-
self.log.info("session_activated", call_id=call_id)
|
204
|
-
self._session_manager.activate_session(call_id)
|
205
|
-
return SwaigFunctionResult("Session activated")
|
206
|
-
else:
|
207
|
-
self.log.warning("session_activation_failed", error="No call_id provided")
|
208
|
-
return SwaigFunctionResult("Failed to activate session: No call_id provided")
|
209
|
-
|
210
|
-
def _handle_hangup_hook(self, args, raw_data):
|
211
|
-
"""
|
212
|
-
Handle the hangup hook function call
|
213
|
-
|
214
|
-
Args:
|
215
|
-
args: Function arguments (empty for this hook)
|
216
|
-
raw_data: Raw request data containing call_id
|
217
|
-
|
218
|
-
Returns:
|
219
|
-
Success response
|
220
|
-
"""
|
221
|
-
call_id = raw_data.get("call_id") if raw_data else None
|
222
|
-
if call_id:
|
223
|
-
self.log.info("session_ended", call_id=call_id)
|
224
|
-
self._session_manager.end_session(call_id)
|
225
|
-
return SwaigFunctionResult("Session ended")
|
226
|
-
else:
|
227
|
-
self.log.warning("session_end_failed", error="No call_id provided")
|
228
|
-
return SwaigFunctionResult("Failed to end session: No call_id provided")
|
229
164
|
|
230
165
|
def _execute_swaig_function(self, function_name: str, args: Optional[Dict[str, Any]] = None, call_id: Optional[str] = None, raw_data: Optional[Dict[str, Any]] = None):
|
231
166
|
"""
|
@@ -52,7 +52,6 @@ class ConciergeAgent(AgentBase):
|
|
52
52
|
welcome_message: Optional[str] = None,
|
53
53
|
name: str = "concierge",
|
54
54
|
route: str = "/concierge",
|
55
|
-
enable_state_tracking: bool = True,
|
56
55
|
**kwargs
|
57
56
|
):
|
58
57
|
"""
|
@@ -67,7 +66,6 @@ class ConciergeAgent(AgentBase):
|
|
67
66
|
welcome_message: Optional custom welcome message
|
68
67
|
name: Agent name for the route
|
69
68
|
route: HTTP route for this agent
|
70
|
-
enable_state_tracking: Whether to enable state tracking (default: True)
|
71
69
|
**kwargs: Additional arguments for AgentBase
|
72
70
|
"""
|
73
71
|
# Initialize the base agent
|
@@ -75,7 +73,6 @@ class ConciergeAgent(AgentBase):
|
|
75
73
|
name=name,
|
76
74
|
route=route,
|
77
75
|
use_pom=True,
|
78
|
-
enable_state_tracking=enable_state_tracking,
|
79
76
|
**kwargs
|
80
77
|
)
|
81
78
|
|
@@ -51,7 +51,6 @@ class FAQBotAgent(AgentBase):
|
|
51
51
|
persona: Optional[str] = None,
|
52
52
|
name: str = "faq_bot",
|
53
53
|
route: str = "/faq",
|
54
|
-
enable_state_tracking: bool = True, # Enable state tracking by default
|
55
54
|
**kwargs
|
56
55
|
):
|
57
56
|
"""
|
@@ -66,7 +65,6 @@ class FAQBotAgent(AgentBase):
|
|
66
65
|
persona: Optional custom personality description
|
67
66
|
name: Agent name for the route
|
68
67
|
route: HTTP route for this agent
|
69
|
-
enable_state_tracking: Whether to enable state tracking (default: True)
|
70
68
|
**kwargs: Additional arguments for AgentBase
|
71
69
|
"""
|
72
70
|
# Initialize the base agent
|
@@ -74,7 +72,6 @@ class FAQBotAgent(AgentBase):
|
|
74
72
|
name=name,
|
75
73
|
route=route,
|
76
74
|
use_pom=True,
|
77
|
-
enable_state_tracking=enable_state_tracking, # Pass state tracking parameter to base
|
78
75
|
**kwargs
|
79
76
|
)
|
80
77
|
|
@@ -45,7 +45,6 @@ class InfoGathererAgent(AgentBase):
|
|
45
45
|
questions: Optional[List[Dict[str, str]]] = None,
|
46
46
|
name: str = "info_gatherer",
|
47
47
|
route: str = "/info_gatherer",
|
48
|
-
enable_state_tracking: bool = True, # Enable state tracking by default for InfoGatherer
|
49
48
|
**kwargs
|
50
49
|
):
|
51
50
|
"""
|
@@ -59,7 +58,6 @@ class InfoGathererAgent(AgentBase):
|
|
59
58
|
- confirm: (Optional) If set to True, the agent will confirm the answer before submitting
|
60
59
|
name: Agent name for the route
|
61
60
|
route: HTTP route for this agent
|
62
|
-
enable_state_tracking: Whether to enable state tracking (default: True)
|
63
61
|
**kwargs: Additional arguments for AgentBase
|
64
62
|
"""
|
65
63
|
# Initialize the base agent
|
@@ -67,7 +65,6 @@ class InfoGathererAgent(AgentBase):
|
|
67
65
|
name=name,
|
68
66
|
route=route,
|
69
67
|
use_pom=True,
|
70
|
-
enable_state_tracking=enable_state_tracking, # Pass state tracking parameter to base
|
71
68
|
**kwargs
|
72
69
|
)
|
73
70
|
|
@@ -41,7 +41,6 @@ class ReceptionistAgent(AgentBase):
|
|
41
41
|
route: str = "/receptionist",
|
42
42
|
greeting: str = "Thank you for calling. How can I help you today?",
|
43
43
|
voice: str = "rime.spore",
|
44
|
-
enable_state_tracking: bool = True, # Enable state tracking by default
|
45
44
|
**kwargs
|
46
45
|
):
|
47
46
|
"""
|
@@ -56,7 +55,6 @@ class ReceptionistAgent(AgentBase):
|
|
56
55
|
route: HTTP route for this agent
|
57
56
|
greeting: Initial greeting message
|
58
57
|
voice: Voice ID to use
|
59
|
-
enable_state_tracking: Whether to enable state tracking (default: True)
|
60
58
|
**kwargs: Additional arguments for AgentBase
|
61
59
|
"""
|
62
60
|
# Initialize the base agent
|
@@ -64,7 +62,6 @@ class ReceptionistAgent(AgentBase):
|
|
64
62
|
name=name,
|
65
63
|
route=route,
|
66
64
|
use_pom=True,
|
67
|
-
enable_state_tracking=enable_state_tracking, # Pass state tracking parameter to base
|
68
65
|
**kwargs
|
69
66
|
)
|
70
67
|
|
@@ -62,7 +62,6 @@ class SurveyAgent(AgentBase):
|
|
62
62
|
max_retries: int = 2,
|
63
63
|
name: str = "survey",
|
64
64
|
route: str = "/survey",
|
65
|
-
enable_state_tracking: bool = True, # Enable state tracking by default
|
66
65
|
**kwargs
|
67
66
|
):
|
68
67
|
"""
|
@@ -83,7 +82,6 @@ class SurveyAgent(AgentBase):
|
|
83
82
|
max_retries: Maximum number of times to retry invalid answers
|
84
83
|
name: Name for the agent (default: "survey")
|
85
84
|
route: HTTP route for the agent (default: "/survey")
|
86
|
-
enable_state_tracking: Whether to enable state tracking (default: True)
|
87
85
|
**kwargs: Additional arguments for AgentBase
|
88
86
|
"""
|
89
87
|
# Initialize the base agent
|
@@ -91,7 +89,6 @@ class SurveyAgent(AgentBase):
|
|
91
89
|
name=name,
|
92
90
|
route=route,
|
93
91
|
use_pom=True,
|
94
|
-
enable_state_tracking=enable_state_tracking, # Pass state tracking parameter to base
|
95
92
|
**kwargs
|
96
93
|
)
|
97
94
|
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# MCP Gateway Skill
|
2
|
+
|
3
|
+
Bridge MCP (Model Context Protocol) servers with SignalWire SWAIG functions, allowing agents to seamlessly interact with MCP-based tools.
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
The MCP Gateway skill connects SignalWire agents to MCP servers through a centralized gateway service. It dynamically discovers and registers MCP tools as SWAIG functions, maintaining session state throughout each call.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- Dynamic tool discovery from MCP servers
|
12
|
+
- Session management tied to SignalWire call IDs
|
13
|
+
- Automatic cleanup on call hangup
|
14
|
+
- Support for multiple MCP services
|
15
|
+
- Selective tool loading
|
16
|
+
- HTTPS support with SSL verification
|
17
|
+
- Retry logic for resilient connections
|
18
|
+
|
19
|
+
## Requirements
|
20
|
+
|
21
|
+
- Running MCP Gateway service
|
22
|
+
- Network access to gateway
|
23
|
+
- Gateway credentials (username/password)
|
24
|
+
|
25
|
+
## Configuration
|
26
|
+
|
27
|
+
### Required Parameters
|
28
|
+
|
29
|
+
Either Basic Auth credentials OR Bearer token:
|
30
|
+
- `gateway_url`: URL of the MCP gateway service (default: "http://localhost:8100")
|
31
|
+
- `auth_user` + `auth_password`: Basic auth credentials
|
32
|
+
- OR `auth_token`: Bearer token for authentication
|
33
|
+
|
34
|
+
### Optional Parameters
|
35
|
+
|
36
|
+
- `services`: Array of services to load (default: all available)
|
37
|
+
- `name`: Service name
|
38
|
+
- `tools`: Array of tool names or "*" for all (default: all)
|
39
|
+
- `session_timeout`: Session timeout in seconds (default: 300)
|
40
|
+
- `tool_prefix`: Prefix for SWAIG function names (default: "mcp_")
|
41
|
+
- `retry_attempts`: Number of retry attempts (default: 3)
|
42
|
+
- `request_timeout`: HTTP request timeout in seconds (default: 30)
|
43
|
+
- `verify_ssl`: Verify SSL certificates (default: true)
|
44
|
+
|
45
|
+
## Usage
|
46
|
+
|
47
|
+
### Basic Usage (All Services)
|
48
|
+
|
49
|
+
```python
|
50
|
+
from signalwire_agents import AgentBase
|
51
|
+
|
52
|
+
class MyAgent(AgentBase):
|
53
|
+
def __init__(self):
|
54
|
+
super().__init__(name="My Agent")
|
55
|
+
|
56
|
+
# Load all available MCP services
|
57
|
+
self.add_skill("mcp_gateway", {
|
58
|
+
"gateway_url": "http://localhost:8080",
|
59
|
+
"auth_user": "admin",
|
60
|
+
"auth_password": "changeme"
|
61
|
+
})
|
62
|
+
|
63
|
+
agent = MyAgent()
|
64
|
+
agent.run()
|
65
|
+
```
|
66
|
+
|
67
|
+
### Selective Service Loading
|
68
|
+
|
69
|
+
```python
|
70
|
+
# Load specific services with specific tools
|
71
|
+
self.add_skill("mcp_gateway", {
|
72
|
+
"gateway_url": "https://gateway.example.com",
|
73
|
+
"auth_user": "admin",
|
74
|
+
"auth_password": "secret",
|
75
|
+
"services": [
|
76
|
+
{
|
77
|
+
"name": "todo",
|
78
|
+
"tools": ["add_todo", "list_todos"] # Only these tools
|
79
|
+
},
|
80
|
+
{
|
81
|
+
"name": "calculator",
|
82
|
+
"tools": "*" # All calculator tools
|
83
|
+
}
|
84
|
+
],
|
85
|
+
"session_timeout": 600,
|
86
|
+
"tool_prefix": "ext_"
|
87
|
+
})
|
88
|
+
```
|
89
|
+
|
90
|
+
### HTTPS with Self-Signed Certificate
|
91
|
+
|
92
|
+
```python
|
93
|
+
self.add_skill("mcp_gateway", {
|
94
|
+
"gateway_url": "https://localhost:8443",
|
95
|
+
"auth_user": "admin",
|
96
|
+
"auth_password": "secret",
|
97
|
+
"verify_ssl": False # For self-signed certificates
|
98
|
+
})
|
99
|
+
```
|
100
|
+
|
101
|
+
### Bearer Token Authentication
|
102
|
+
|
103
|
+
```python
|
104
|
+
self.add_skill("mcp_gateway", {
|
105
|
+
"gateway_url": "https://gateway.example.com",
|
106
|
+
"auth_token": "your-bearer-token-here",
|
107
|
+
"services": [{
|
108
|
+
"name": "todo"
|
109
|
+
}]
|
110
|
+
})
|
111
|
+
```
|
112
|
+
|
113
|
+
## Generated Functions
|
114
|
+
|
115
|
+
The skill dynamically generates SWAIG functions based on discovered MCP tools. Function names follow the pattern:
|
116
|
+
|
117
|
+
`{tool_prefix}{service_name}_{tool_name}`
|
118
|
+
|
119
|
+
For example, with default settings:
|
120
|
+
- `mcp_todo_add_todo` - Add a todo item
|
121
|
+
- `mcp_todo_list_todos` - List todo items
|
122
|
+
- `mcp_calculator_add` - Calculator addition
|
123
|
+
|
124
|
+
## Example Conversations
|
125
|
+
|
126
|
+
### Using Todo Service
|
127
|
+
|
128
|
+
```
|
129
|
+
User: "Add a task to buy milk"
|
130
|
+
Assistant: "I'll add that to your todo list."
|
131
|
+
[Calls mcp_todo_add_todo with text="buy milk"]
|
132
|
+
Assistant: "I've added 'buy milk' to your todo list."
|
133
|
+
|
134
|
+
User: "What's on my todo list?"
|
135
|
+
Assistant: "Let me check your todos."
|
136
|
+
[Calls mcp_todo_list_todos]
|
137
|
+
Assistant: "Here are your current todos:
|
138
|
+
○ #1 [medium] buy milk"
|
139
|
+
```
|
140
|
+
|
141
|
+
### Multiple Services
|
142
|
+
|
143
|
+
```
|
144
|
+
User: "Add 'finish report' to my todos and calculate 15% of 200"
|
145
|
+
Assistant: "I'll add that todo and do the calculation for you."
|
146
|
+
[Calls mcp_todo_add_todo with text="finish report"]
|
147
|
+
[Calls mcp_calculator_percent with value=200, percent=15]
|
148
|
+
Assistant: "I've added 'finish report' to your todos. 15% of 200 is 30."
|
149
|
+
```
|
150
|
+
|
151
|
+
## Session Management
|
152
|
+
|
153
|
+
- Each SignalWire call gets its own MCP session
|
154
|
+
- Sessions persist across multiple tool calls
|
155
|
+
- Automatic cleanup on call hangup
|
156
|
+
- Configurable timeout for inactive sessions
|
157
|
+
|
158
|
+
### Custom Session ID
|
159
|
+
|
160
|
+
You can override the session ID by setting `mcp_call_id` in global_data:
|
161
|
+
|
162
|
+
```python
|
163
|
+
# In your agent code
|
164
|
+
self.set_global_data({
|
165
|
+
"mcp_call_id": "custom-session-123"
|
166
|
+
})
|
167
|
+
|
168
|
+
# Or in a SWAIG function
|
169
|
+
result = SwaigFunctionResult("Session changed")
|
170
|
+
result.add_action("set_global_data", {"mcp_call_id": "new-session-456"})
|
171
|
+
```
|
172
|
+
|
173
|
+
This is useful for:
|
174
|
+
- Managing multiple MCP sessions within a single call
|
175
|
+
- Sharing MCP sessions across different calls
|
176
|
+
- Custom session management strategies
|
177
|
+
|
178
|
+
## Troubleshooting
|
179
|
+
|
180
|
+
### Gateway Connection Failed
|
181
|
+
|
182
|
+
Check:
|
183
|
+
1. Gateway service is running
|
184
|
+
2. Correct URL and credentials
|
185
|
+
3. Network connectivity
|
186
|
+
4. Firewall rules
|
187
|
+
|
188
|
+
### SSL Certificate Errors
|
189
|
+
|
190
|
+
For self-signed certificates:
|
191
|
+
```python
|
192
|
+
"verify_ssl": False
|
193
|
+
```
|
194
|
+
|
195
|
+
For custom CA certificates, ensure they're in the system trust store.
|
196
|
+
|
197
|
+
### Tool Not Found
|
198
|
+
|
199
|
+
Verify:
|
200
|
+
1. Service name is correct
|
201
|
+
2. Tool name matches exactly
|
202
|
+
3. Tool is included in service configuration
|
203
|
+
4. MCP server is returning tools correctly
|
204
|
+
|
205
|
+
### Session Timeouts
|
206
|
+
|
207
|
+
Increase timeout if needed:
|
208
|
+
```python
|
209
|
+
"session_timeout": 600 # 10 minutes
|
210
|
+
```
|
211
|
+
|
212
|
+
## Gateway Setup
|
213
|
+
|
214
|
+
To run the MCP Gateway service:
|
215
|
+
|
216
|
+
```bash
|
217
|
+
cd mcp_gateway
|
218
|
+
python3 gateway_service.py
|
219
|
+
|
220
|
+
# Or with custom config
|
221
|
+
python3 gateway_service.py -c myconfig.json
|
222
|
+
```
|
223
|
+
|
224
|
+
## Security Considerations
|
225
|
+
|
226
|
+
1. Always use HTTPS in production
|
227
|
+
2. Use strong authentication credentials
|
228
|
+
3. Limit service access to required tools only
|
229
|
+
4. Monitor gateway logs for suspicious activity
|
230
|
+
5. Set appropriate session timeouts
|
@@ -0,0 +1 @@
|
|
1
|
+
"""MCP Gateway Skill for SignalWire Agents"""
|