365 lines
14 KiB
Python
365 lines
14 KiB
Python
"""
|
|
Setup script for MCP Browser.
|
|
|
|
This package is developed exclusively by AI assistants.
|
|
"""
|
|
|
|
from setuptools import setup, find_packages, Command
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
from pathlib import Path
|
|
import asyncio
|
|
|
|
|
|
class GenerateAIDocs(Command):
|
|
"""Generate AI-friendly documentation."""
|
|
description = 'Generate documentation for AI navigation'
|
|
user_options = []
|
|
|
|
def initialize_options(self):
|
|
pass
|
|
|
|
def finalize_options(self):
|
|
pass
|
|
|
|
def run(self):
|
|
"""Run all AI documentation generators."""
|
|
print("Generating AI-friendly documentation...")
|
|
|
|
# 1. Generate API documentation
|
|
try:
|
|
subprocess.run([sys.executable, '-m', 'pydoc', '-w', '.'],
|
|
cwd='mcp_browser', check=True)
|
|
print("✓ Generated pydoc API documentation")
|
|
except Exception as e:
|
|
print(f"⚠ pydoc generation failed: {e}")
|
|
|
|
# 2. Generate ctags for code navigation
|
|
try:
|
|
subprocess.run(['ctags', '-R', '--languages=Python',
|
|
'--python-kinds=-i', '-f', '.tags'], check=True)
|
|
print("✓ Generated ctags file")
|
|
except FileNotFoundError:
|
|
print("⚠ ctags not installed (install with: apt-get install universal-ctags)")
|
|
except Exception as e:
|
|
print(f"⚠ ctags generation failed: {e}")
|
|
|
|
# 3. Generate structure documentation
|
|
self.generate_structure_doc()
|
|
|
|
# 4. Generate API summary
|
|
self.generate_api_summary()
|
|
|
|
print("\nAI documentation generation complete!")
|
|
print("Files created:")
|
|
print(" - docs/STRUCTURE.md - Project structure overview")
|
|
print(" - docs/API_SUMMARY.md - API quick reference")
|
|
print(" - .tags - ctags for code navigation")
|
|
print(" - *.html - pydoc HTML documentation")
|
|
|
|
def generate_structure_doc(self):
|
|
"""Generate project structure documentation."""
|
|
structure = []
|
|
for root, dirs, files in os.walk('.'):
|
|
# Skip hidden directories and __pycache__
|
|
dirs[:] = [d for d in dirs if not d.startswith('.') and d != '__pycache__']
|
|
|
|
level = root.replace('.', '').count(os.sep)
|
|
indent = ' ' * level
|
|
structure.append(f"{indent}{os.path.basename(root)}/")
|
|
|
|
subindent = ' ' * (level + 1)
|
|
for file in sorted(files):
|
|
if file.endswith('.py'):
|
|
structure.append(f"{subindent}{file}")
|
|
|
|
os.makedirs('docs', exist_ok=True)
|
|
with open('docs/STRUCTURE.md', 'w') as f:
|
|
f.write("# Project Structure\n\n")
|
|
f.write("```\n")
|
|
f.write('\n'.join(structure))
|
|
f.write("\n```\n")
|
|
|
|
def generate_api_summary(self):
|
|
"""Generate API summary for quick reference."""
|
|
api_summary = []
|
|
api_summary.append("# MCP Browser API Summary\n")
|
|
api_summary.append("## Main Classes\n")
|
|
|
|
# Extract main classes and methods
|
|
main_files = [
|
|
('mcp_browser/proxy.py', 'MCPBrowser'),
|
|
('mcp_browser/registry.py', 'ToolRegistry'),
|
|
('mcp_browser/server.py', 'MCPServer'),
|
|
('mcp_browser/multi_server.py', 'MultiServerManager'),
|
|
]
|
|
|
|
for file_path, class_name in main_files:
|
|
if os.path.exists(file_path):
|
|
api_summary.append(f"\n### {class_name} ({file_path})\n")
|
|
# Simple method extraction (could be enhanced)
|
|
try:
|
|
with open(file_path, 'r') as f:
|
|
content = f.read()
|
|
lines = content.split('\n')
|
|
for i, line in enumerate(lines):
|
|
if line.strip().startswith('def ') and not line.strip().startswith('def _'):
|
|
method = line.strip()[4:].split('(')[0]
|
|
api_summary.append(f"- `{method}()`")
|
|
except Exception:
|
|
pass
|
|
|
|
os.makedirs('docs', exist_ok=True)
|
|
with open('docs/API_SUMMARY.md', 'w') as f:
|
|
f.write('\n'.join(api_summary))
|
|
|
|
|
|
class TestCommand(Command):
|
|
"""Run all tests including integration tests."""
|
|
description = 'Run unit and integration tests'
|
|
user_options = []
|
|
|
|
def initialize_options(self):
|
|
pass
|
|
|
|
def finalize_options(self):
|
|
pass
|
|
|
|
def run(self):
|
|
"""Run all tests."""
|
|
print("Running MCP Browser Tests")
|
|
print("=" * 50)
|
|
|
|
# Run pytest for unit tests
|
|
print("\nRunning unit tests with pytest...")
|
|
try:
|
|
subprocess.run([sys.executable, '-m', 'pytest', 'tests/', '-v',
|
|
'--ignore=tests/test_integration.py'], check=True)
|
|
print("✓ Unit tests passed")
|
|
except subprocess.CalledProcessError:
|
|
print("✗ Unit tests failed")
|
|
sys.exit(1)
|
|
except FileNotFoundError:
|
|
print("⚠ pytest not installed. Run: pip install -e .[dev]")
|
|
sys.exit(1)
|
|
|
|
# Run integration tests
|
|
print("\nRunning integration tests...")
|
|
try:
|
|
# Run integration test directly
|
|
subprocess.run([sys.executable, 'tests/test_integration.py'], check=True)
|
|
print("✓ Integration tests passed")
|
|
except subprocess.CalledProcessError:
|
|
print("✗ Integration tests failed")
|
|
sys.exit(1)
|
|
|
|
print("\n" + "=" * 50)
|
|
print("✅ All tests passed!")
|
|
|
|
|
|
class GenerateApiDocs(Command):
|
|
"""Generate comprehensive MCP API documentation for AI consumption."""
|
|
description = 'Generate JSON API documentation of all MCP servers and tools'
|
|
user_options = []
|
|
|
|
def initialize_options(self):
|
|
pass
|
|
|
|
def finalize_options(self):
|
|
pass
|
|
|
|
def run(self):
|
|
"""Generate comprehensive MCP API documentation."""
|
|
print("Generating MCP API Documentation...")
|
|
|
|
import asyncio
|
|
from datetime import datetime
|
|
|
|
async def generate_docs():
|
|
# Import here to avoid circular dependencies
|
|
from mcp_browser import MCPBrowser
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from pathlib import Path
|
|
import os
|
|
|
|
# Initialize MCP Browser with config from standard location
|
|
config_path = Path.home() / ".config" / "mcp-browser" / "config.yaml"
|
|
|
|
print(f"Loading config from: {config_path}")
|
|
if not config_path.exists():
|
|
print(f"⚠ Config file not found, creating default")
|
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Initialize with config and built-in servers
|
|
browser = MCPBrowser(
|
|
config_path=config_path if config_path.exists() else None,
|
|
enable_builtin_servers=True
|
|
)
|
|
|
|
print("Waiting for server initialization...")
|
|
# Wait for initialization of servers
|
|
await asyncio.sleep(3)
|
|
|
|
# Perform tool discovery using the browser's call method
|
|
print("Discovering tools from all servers...")
|
|
try:
|
|
tools_response = await browser.call({
|
|
"jsonrpc": "2.0",
|
|
"id": 1,
|
|
"method": "tools/list"
|
|
})
|
|
print(f"Tools response: {len(tools_response.get('result', {}).get('tools', []))} tools found")
|
|
except Exception as e:
|
|
print(f"Warning: Could not discover tools: {e}")
|
|
|
|
print(f"Registry has {len(browser.registry.raw_tool_list)} tools")
|
|
|
|
# Set server metadata for documentation
|
|
if hasattr(browser, 'config') and browser.config:
|
|
server_metadata = {
|
|
"servers": {}
|
|
}
|
|
for name, config in browser.config.servers.items():
|
|
server_metadata["servers"][name] = {
|
|
"command": getattr(config, 'command', []),
|
|
"description": getattr(config, 'description', ''),
|
|
"status": "configured",
|
|
"env": getattr(config, 'env', {}),
|
|
"tools": [] # Will be populated by get_full_api_documentation
|
|
}
|
|
browser.registry.set_metadata(server_metadata)
|
|
|
|
# Get comprehensive API documentation
|
|
api_doc = browser.registry.get_full_api_documentation()
|
|
api_doc["generation_timestamp"] = datetime.now().isoformat()
|
|
|
|
# Add runtime server information
|
|
if hasattr(browser, 'multi_server') and browser.multi_server:
|
|
server_status = {}
|
|
for name, server in browser.multi_server.servers.items():
|
|
server_status[name] = {
|
|
"status": "active" if hasattr(server, 'process') and server.process else "inactive",
|
|
"pid": getattr(server.process, 'pid', None) if hasattr(server, 'process') and server.process else None
|
|
}
|
|
api_doc["runtime_status"] = server_status
|
|
|
|
# Write to file
|
|
output_file = Path("mcp_api_documentation.json")
|
|
with open(output_file, 'w') as f:
|
|
import json
|
|
json.dump(api_doc, f, indent=2)
|
|
|
|
print(f"✓ Generated comprehensive MCP API documentation")
|
|
print(f"✓ Output: {output_file.absolute()}")
|
|
print(f"✓ Total servers: {api_doc['total_servers']}")
|
|
print(f"✓ Total tools: {api_doc['total_tools']}")
|
|
|
|
# Also print summary for immediate use
|
|
print("\n" + "=" * 60)
|
|
print("QUICK REFERENCE:")
|
|
print("=" * 60)
|
|
|
|
builtin = api_doc.get("builtin", {})
|
|
print(f"Built-in tools ({builtin.get('tool_count', 0)}):")
|
|
for tool in builtin.get("tools", [])[:5]: # Show first 5
|
|
print(f" - {tool.get('name', 'Unknown')}")
|
|
if builtin.get('tool_count', 0) > 5:
|
|
print(f" ... and {builtin.get('tool_count', 0) - 5} more")
|
|
|
|
print(f"\nExternal servers ({len(api_doc.get('servers', {}))}):")
|
|
for server_name, server_info in api_doc.get("servers", {}).items():
|
|
print(f" - {server_name}: {server_info.get('tool_count', 0)} tools")
|
|
|
|
print(f"\nDiscovery patterns available in documentation:")
|
|
for pattern_name, pattern in api_doc.get("discovery_patterns", {}).items():
|
|
print(f" - {pattern_name}: {pattern}")
|
|
|
|
# Clean up
|
|
await browser.close()
|
|
|
|
except Exception as e:
|
|
print(f"✗ Failed to generate API documentation: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
asyncio.run(generate_docs())
|
|
|
|
|
|
# Read long description
|
|
with open("README.md", "r", encoding="utf-8") as fh:
|
|
long_description = fh.read()
|
|
|
|
|
|
setup(
|
|
name="mcp-browser",
|
|
version="0.2.0",
|
|
description="A generic MCP browser with context optimization for AI systems",
|
|
long_description=long_description,
|
|
long_description_content_type="text/markdown",
|
|
author="Claude4Ξlope",
|
|
author_email="xilope@esus.name",
|
|
url="https://github.com/Xilope0/mcp-browser",
|
|
packages=find_packages(include=['mcp_browser*', 'mcp_servers*']),
|
|
package_data={
|
|
'mcp_browser': ['py.typed'],
|
|
'mcp_servers': ['**/*.py'],
|
|
'mcp_servers.screen': ['*.py'],
|
|
'mcp_servers.memory': ['*.py'],
|
|
'mcp_servers.pattern_manager': ['*.py'],
|
|
'mcp_servers.onboarding': ['*.py'],
|
|
},
|
|
include_package_data=True,
|
|
install_requires=[
|
|
"aiofiles>=23.0.0",
|
|
"jsonpath-ng>=1.6.0",
|
|
"pyyaml>=6.0",
|
|
"httpx>=0.27",
|
|
"aiohttp>=3.9",
|
|
"typing-extensions>=4.0.0;python_version<'3.11'",
|
|
],
|
|
extras_require={
|
|
'dev': [
|
|
'pytest>=7.0.0',
|
|
'pytest-asyncio>=0.21.0',
|
|
'black>=23.0.0',
|
|
'mypy>=1.0.0',
|
|
'ruff>=0.1.0',
|
|
],
|
|
'docs': [
|
|
'sphinx>=6.0.0',
|
|
'sphinx-rtd-theme>=1.3.0',
|
|
'myst-parser>=2.0.0',
|
|
],
|
|
},
|
|
python_requires=">=3.8",
|
|
entry_points={
|
|
"console_scripts": [
|
|
"mcp-browser=mcp_browser.__main__:main",
|
|
"mcp-browser-daemon=mcp_browser.daemon_main:main",
|
|
"mcp-browser-client=mcp_browser.client_main:main",
|
|
],
|
|
},
|
|
cmdclass={
|
|
'aidocs': GenerateAIDocs,
|
|
'test': TestCommand,
|
|
'gen_apidoc': GenerateApiDocs,
|
|
},
|
|
license="GPL-3.0-or-later",
|
|
classifiers=[
|
|
"Development Status :: 3 - Alpha",
|
|
"Intended Audience :: Developers",
|
|
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3.8",
|
|
"Programming Language :: Python :: 3.9",
|
|
"Programming Language :: Python :: 3.10",
|
|
"Programming Language :: Python :: 3.11",
|
|
"Programming Language :: Python :: 3.12",
|
|
],
|
|
keywords="mcp model-context-protocol ai llm tools json-rpc",
|
|
)
|