feat: add user-configurable MCP connections

- manage_mcp_connection tool: add/remove/enable/disable/list MCP servers
- mcp_loader: dynamic connection with OAuth/bearer/none auth, token caching
- Secrets stored in SSM, never in DynamoDB
- MCP clients loaded per-session and cleaned up in finally block
This commit is contained in:
daniel
2026-05-13 21:55:01 -05:00
parent 74f74ef877
commit bdd334b6fb
3 changed files with 254 additions and 2 deletions

View File

@@ -19,7 +19,10 @@ import tools.scheduler as _scheduler_module
from tools.home_assistant import home_assistant, set_ha_config
from tools.google_workspace import list_calendars, get_calendar_events, list_gmail_messages, get_gmail_message
from tools.send_file import send_file as _send_file_impl
from tools.mcp_tools import manage_mcp_connection
import tools.mcp_tools as _mcp_tools_module
import tools.google_workspace as _gws
import mcp_loader
import httpx
import botocore.auth
import botocore.awsrequest
@@ -313,6 +316,7 @@ async def main(payload: dict, context):
_current_chat_id = chat_id
_scheduler_module._current_actor_id = actor_id
_scheduler_module._current_chat_id = chat_id
_mcp_tools_module._current_actor_id = actor_id
# Run compaction if flagged from previous invocation (trims old events before load)
memory_manager.check_and_compact(actor_id, session_id)
@@ -381,15 +385,20 @@ async def main(payload: dict, context):
base_tools = [web_search, web_fetch, read_workspace_file, write_workspace_file,
home_assistant, connect_google_account, list_google_accounts, remove_google_account,
manage_service, schedule_reminder, list_reminders, cancel_reminder,
manage_service, manage_mcp_connection, schedule_reminder, list_reminders, cancel_reminder,
list_calendars, get_calendar_events, list_gmail_messages, get_gmail_message,
run_code, send_file]
# Load user's dynamic MCP connections
mcp_connections = services.get('mcp_connections', [])
mcp_clients, _mcp_to_close = mcp_loader.load_mcp_tools(mcp_connections, actor_id)
all_tools = base_tools + mcp_clients
agent = Agent(
model=model,
system_prompt=system_prompt,
session_manager=session_manager,
tools=base_tools,
tools=all_tools,
)
final_message = None
@@ -407,6 +416,7 @@ async def main(payload: dict, context):
finally:
_typing_active = False
session_manager.close()
mcp_loader.close_mcp_clients(_mcp_to_close)
# Check if session exceeds window — flag for compaction on next invocation
memory_manager.check_window_and_flag(actor_id, session_id)