Files
javis_bot/tests/test_mcp_e2e.py
javis-bot c4abf63f38
Some checks failed
Release / semantic-release (push) Successful in 59s
tests / Unit tests (Linux, Python 3.11) (push) Successful in 13m45s
Release / build-linux (push) Failing after 7m47s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled
Add Discord-native hybrid front-end for Jarvis (bot + bridge)
Transform isair/jarvis into a Discord-controlled voice assistant running on
the Ubuntu VNC desktop, keeping the mature ~39k-line Python brain intact.

- bot/ (Node + bun, discord.js): /자비스 slash commands (ephemeral),
  voice channel join + voice receive/playback, pluggable VNC screen broadcast
  (selfbot live / noVNC / screenshot)
- bridge/ (Python, Flask): wraps jarvis STT + run_reply_engine + Piper TTS
  behind a thin localhost HTTP API
- .env.example, scripts/ (start_bridge/start_bot/dev), README rewrite,
  docs/language-comparison.md and docs/vnc-xfce-setup.md

Language decision: hybrid (Python brain + Node/bun Discord layer) because
Discord blocks bot video; native screen broadcast only works via a Node
selfbot library.
2026-06-09 14:51:05 +09:00

233 lines
7.7 KiB
Python

"""
End-to-end tests for MCP tool integration.
These tests verify that the complete MCP integration pipeline works:
1. Configuration loading
2. MCP tool discovery
3. Tool registration and availability
4. Reply engine integration
Note: These tests are marked as @pytest.mark.e2e and may not run in basic CI environments.
They are intended for local development and git hook testing.
"""
import sys
import os
import json
import tempfile
from pathlib import Path
import pytest
# Add project root to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from src.jarvis.tools.registry import discover_mcp_tools, generate_tools_description
from src.jarvis.config import load_settings
@pytest.mark.e2e
def test_configuration_loading():
"""Test that MCP configuration is properly loaded."""
print("🔧 Testing MCP configuration loading...")
try:
cfg = load_settings()
mcps = getattr(cfg, 'mcps', {})
print(f" Found {len(mcps)} configured MCP servers")
for server_name, server_config in mcps.items():
command = server_config.get('command', 'unknown')
print(f" - {server_name}: {command}")
# Assert that we have at least some MCP configuration
assert isinstance(mcps, dict), "MCP configuration should be a dictionary"
print(" ✅ Configuration loading successful")
except Exception as e:
print(f" ❌ Failed to load configuration: {e}")
assert False, f"Failed to load configuration: {e}"
@pytest.mark.e2e
def test_mcp_discovery_with_mock():
"""Test MCP discovery with mocked servers."""
print("🔍 Testing MCP tool discovery (mocked)...")
# Create a fake MCP configuration
fake_mcps = {
"test-server": {
"command": "echo",
"args": ["test"]
}
}
# Mock the MCPClient to avoid actual server connections
from unittest.mock import patch, Mock
class FakeMCPClient:
def __init__(self, config):
self.config = config
def list_tools(self, server_name):
return [
{"name": "read", "description": "Read a file"},
{"name": "write", "description": "Write a file"},
{"name": "list", "description": "List directory contents"}
]
try:
with patch('src.jarvis.tools.registry.MCPClient', FakeMCPClient):
mcp_tools, _errors = discover_mcp_tools(fake_mcps)
expected_tools = {
"test-server__read",
"test-server__write",
"test-server__list"
}
actual_tools = set(mcp_tools.keys())
assert actual_tools == expected_tools, f"Tool mismatch. Expected: {expected_tools}, Got: {actual_tools}"
print(f" ✅ Successfully discovered {len(mcp_tools)} tools")
for tool_name in mcp_tools:
print(f" - {tool_name}")
except Exception as e:
print(f" ❌ Discovery failed: {e}")
import traceback
traceback.print_exc()
assert False, f"Discovery failed: {e}"
@pytest.mark.e2e
def test_tool_registration_in_descriptions():
"""Test that discovered tools appear in tool descriptions."""
print("📝 Testing tool description generation...")
from src.jarvis.tools.registry import ToolSpec
# Create mock MCP tools
mock_mcp_tools = {
"server1__tool1": ToolSpec(
name="server1__tool1",
description="Test tool 1 from server1",
inputSchema={
"type": "object",
"properties": {},
"required": []
}
),
"server2__tool2": ToolSpec(
name="server2__tool2",
description="Test tool 2 from server2",
inputSchema={
"type": "object",
"properties": {},
"required": []
}
)
}
try:
allowed_tools = ["screenshot", "webSearch", "server1__tool1", "server2__tool2"]
description = generate_tools_description(allowed_tools, mock_mcp_tools)
# Check that MCP tools appear in description
success = True
for tool_name in mock_mcp_tools:
if tool_name not in description:
print(f" ❌ Tool {tool_name} not found in description")
success = False
assert success, "Not all MCP tools appear in descriptions"
print(" ✅ All MCP tools appear in descriptions")
print(f" 📄 Description length: {len(description)} characters")
except Exception as e:
print(f" ❌ Description generation failed: {e}")
assert False, f"Description generation failed: {e}"
@pytest.mark.e2e
def test_tool_name_format():
"""Test that tool names follow the server__toolname format."""
print("🏷️ Testing tool naming convention...")
from unittest.mock import patch
class FakeMCPClient:
def __init__(self, config):
self.config = config
def list_tools(self, server_name):
if server_name == "my-server":
return [
{"name": "tool_with_underscores", "description": "Test tool"},
{"name": "tool-with-dashes", "description": "Another test tool"},
{"name": "simpletool", "description": "Simple tool"}
]
return []
try:
with patch('src.jarvis.tools.registry.MCPClient', FakeMCPClient):
mcps_config = {"my-server": {"command": "test"}}
mcp_tools, _errors = discover_mcp_tools(mcps_config)
expected_names = {
"my-server__tool_with_underscores",
"my-server__tool-with-dashes",
"my-server__simpletool"
}
actual_names = set(mcp_tools.keys())
assert actual_names == expected_names, f"Naming mismatch. Expected: {expected_names}, Got: {actual_names}"
print(" ✅ Tool naming convention is correct")
for name in actual_names:
print(f" - {name}")
except Exception as e:
print(f" ❌ Naming test failed: {e}")
assert False, f"Naming test failed: {e}"
@pytest.mark.e2e
def test_error_handling():
"""Test that MCP errors are handled gracefully."""
print("⚠️ Testing error handling...")
from unittest.mock import patch
class FaultyMCPClient:
def __init__(self, config):
pass
def list_tools(self, server_name):
if server_name == "good-server":
return [{"name": "working_tool", "description": "This works"}]
elif server_name == "bad-server":
raise Exception("Server connection failed")
return []
try:
with patch('src.jarvis.tools.registry.MCPClient', FaultyMCPClient):
mcps_config = {
"good-server": {"command": "good"},
"bad-server": {"command": "bad"},
"empty-server": {"command": "empty"}
}
mcp_tools, mcp_errors = discover_mcp_tools(mcps_config)
# Should only get tools from the good server
if len(mcp_tools) == 1 and "good-server__working_tool" in mcp_tools:
print(" ✅ Error handling works correctly")
print(" - Good server tools discovered")
print(" - Bad server errors handled gracefully")
else:
print(f" ❌ Expected 1 tool, got {len(mcp_tools)}: {list(mcp_tools.keys())}")
assert False, f"Expected 1 tool, got {len(mcp_tools)}: {list(mcp_tools.keys())}"
except Exception as e:
print(f" ❌ Error handling test failed: {e}")
assert False, f"Error handling test failed: {e}"