universal-agent-context 0.2.0__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 (47) hide show
  1. uacs/__init__.py +12 -0
  2. uacs/adapters/__init__.py +19 -0
  3. uacs/adapters/agent_skill_adapter.py +202 -0
  4. uacs/adapters/agents_md_adapter.py +330 -0
  5. uacs/adapters/base.py +261 -0
  6. uacs/adapters/clinerules_adapter.py +39 -0
  7. uacs/adapters/cursorrules_adapter.py +39 -0
  8. uacs/api.py +262 -0
  9. uacs/cli/__init__.py +6 -0
  10. uacs/cli/context.py +349 -0
  11. uacs/cli/main.py +195 -0
  12. uacs/cli/mcp.py +115 -0
  13. uacs/cli/memory.py +142 -0
  14. uacs/cli/packages.py +309 -0
  15. uacs/cli/skills.py +144 -0
  16. uacs/cli/utils.py +24 -0
  17. uacs/config/repositories.yaml +26 -0
  18. uacs/context/__init__.py +0 -0
  19. uacs/context/agent_context.py +406 -0
  20. uacs/context/shared_context.py +661 -0
  21. uacs/context/unified_context.py +332 -0
  22. uacs/mcp_server_entry.py +80 -0
  23. uacs/memory/__init__.py +5 -0
  24. uacs/memory/simple_memory.py +255 -0
  25. uacs/packages/__init__.py +26 -0
  26. uacs/packages/manager.py +413 -0
  27. uacs/packages/models.py +60 -0
  28. uacs/packages/sources.py +270 -0
  29. uacs/protocols/__init__.py +5 -0
  30. uacs/protocols/mcp/__init__.py +8 -0
  31. uacs/protocols/mcp/manager.py +77 -0
  32. uacs/protocols/mcp/skills_server.py +700 -0
  33. uacs/skills_validator.py +367 -0
  34. uacs/utils/__init__.py +5 -0
  35. uacs/utils/paths.py +24 -0
  36. uacs/visualization/README.md +132 -0
  37. uacs/visualization/__init__.py +36 -0
  38. uacs/visualization/models.py +195 -0
  39. uacs/visualization/static/index.html +857 -0
  40. uacs/visualization/storage.py +402 -0
  41. uacs/visualization/visualization.py +328 -0
  42. uacs/visualization/web_server.py +364 -0
  43. universal_agent_context-0.2.0.dist-info/METADATA +873 -0
  44. universal_agent_context-0.2.0.dist-info/RECORD +47 -0
  45. universal_agent_context-0.2.0.dist-info/WHEEL +4 -0
  46. universal_agent_context-0.2.0.dist-info/entry_points.txt +2 -0
  47. universal_agent_context-0.2.0.dist-info/licenses/LICENSE +21 -0
uacs/cli/context.py ADDED
@@ -0,0 +1,349 @@
1
+ """CLI commands for context management and visualization."""
2
+
3
+ from pathlib import Path
4
+
5
+ import typer
6
+ from rich.console import Console
7
+ from rich.markdown import Markdown
8
+ from rich.panel import Panel
9
+
10
+ from uacs import UACS
11
+ from uacs.visualization import ContextVisualizer
12
+ from uacs.cli.utils import get_project_root
13
+
14
+ app = typer.Typer(help="Manage shared context and compression")
15
+ console = Console()
16
+
17
+
18
+ def get_uacs() -> UACS:
19
+ """Get UACS instance for current project."""
20
+ return UACS(get_project_root())
21
+
22
+
23
+ @app.command("stats")
24
+ def show_stats():
25
+ """Show context and token usage statistics."""
26
+ uacs = get_uacs()
27
+
28
+ # Get stats from UACS
29
+ stats = uacs.get_stats()
30
+ token_stats = uacs.get_token_stats()
31
+ context_stats = stats.get("context", {})
32
+
33
+ # Render stats
34
+ console.print("\n[bold cyan]📊 Context Statistics[/bold cyan]\n")
35
+
36
+ console.print("[bold]Token Usage:[/bold]")
37
+ console.print(f" AGENTS.md: {token_stats['agents_md_tokens']:>6,} tokens")
38
+ console.print(f" Agent Skills: {token_stats['skills_tokens']:>6,} tokens")
39
+ console.print(
40
+ f" Shared Context: {token_stats['shared_context_tokens']:>6,} tokens"
41
+ )
42
+ console.print(
43
+ f" [dim]Total: {token_stats['total_potential_tokens']:>6,} tokens[/dim]"
44
+ )
45
+
46
+ console.print("\n[bold]Compression:[/bold]")
47
+ console.print(f" Tokens Saved: {token_stats['tokens_saved_by_compression']:>6,}")
48
+ console.print(f" Compression: {context_stats['compression_ratio']:>6}")
49
+ console.print(f" Storage: {context_stats['storage_size_mb']:>6.2f} MB")
50
+
51
+ console.print("\n[bold]Entries:[/bold]")
52
+ console.print(f" Context Entries: {context_stats['entry_count']:>3}")
53
+ console.print(f" Summaries: {context_stats['summary_count']:>3}")
54
+
55
+
56
+ @app.command("visualize")
57
+ def visualize_context(
58
+ update_interval: float = typer.Option(
59
+ 2.0, "--interval", "-i", help="Update interval in seconds"
60
+ ),
61
+ ):
62
+ """Launch live context visualization dashboard."""
63
+ uacs = get_uacs()
64
+ viz = ContextVisualizer(console)
65
+
66
+ console.print("[cyan]Starting live dashboard...[/cyan]")
67
+ console.print("[dim]Press Ctrl+C to exit[/dim]\n")
68
+
69
+ try:
70
+ viz.live_dashboard(uacs.shared_context, update_interval)
71
+ except KeyboardInterrupt:
72
+ console.print("\n[yellow]Dashboard closed[/yellow]")
73
+
74
+
75
+ @app.command("graph")
76
+ def show_graph():
77
+ """Show context relationship graph."""
78
+ uacs = get_uacs()
79
+ viz = ContextVisualizer(console)
80
+
81
+ graph = uacs.shared_context.get_context_graph()
82
+ console.print(viz.render_context_graph(graph))
83
+
84
+
85
+ @app.command("compress")
86
+ def compress_context(
87
+ force: bool = typer.Option(
88
+ False, "--force", help="Force compression even if not needed"
89
+ ),
90
+ ):
91
+ """Manually trigger context compression."""
92
+ uacs = get_uacs()
93
+
94
+ before_stats = uacs.shared_context.get_stats()
95
+ before_tokens = before_stats["total_tokens"]
96
+
97
+ console.print("🗜️ Compressing context...")
98
+
99
+ uacs.unified_context.optimize_context()
100
+
101
+ after_stats = uacs.shared_context.get_stats()
102
+ after_tokens = after_stats["total_tokens"]
103
+
104
+ viz = ContextVisualizer(console)
105
+ console.print(
106
+ viz.render_compression_viz(before_tokens, after_tokens, "auto-summary")
107
+ )
108
+
109
+ console.print("\n[green]✓[/green] Compression complete")
110
+ console.print(
111
+ f" Created {after_stats['summary_count'] - before_stats['summary_count']} new summaries"
112
+ )
113
+
114
+
115
+ @app.command("report")
116
+ def compression_report():
117
+ """Show detailed compression report."""
118
+ uacs = get_uacs()
119
+ report = uacs.unified_context.get_compression_report()
120
+
121
+ md = Markdown(report)
122
+ console.print(Panel(md, title="Compression Report", border_style="cyan"))
123
+
124
+
125
+ @app.command("export")
126
+ def export_config(
127
+ output: Path = typer.Option(
128
+ Path("unified-context.json"), "--output", "-o", help="Output file path"
129
+ ),
130
+ ):
131
+ """Export unified context configuration."""
132
+ uacs = get_uacs()
133
+
134
+ uacs.unified_context.export_unified_config(output)
135
+
136
+ console.print(f"[green]✓[/green] Exported configuration to {output}")
137
+
138
+ # Show summary
139
+ caps = uacs.unified_context.get_unified_capabilities()
140
+ console.print(f"\n Skills: {len(caps['available_skills'])}")
141
+ console.print(f" AGENTS.md: {'✓' if caps['agents_md_loaded'] else '✗'}")
142
+ console.print(f" Context entries: {caps['shared_context_stats']['entry_count']}")
143
+
144
+
145
+ @app.command("snapshot")
146
+ def create_snapshot(name: str = typer.Argument(..., help="Snapshot name")):
147
+ """Create snapshot of current context state."""
148
+ uacs = get_uacs()
149
+
150
+ snapshot = uacs.unified_context.create_snapshot(name)
151
+
152
+ console.print(f"[green]✓[/green] Created snapshot: [cyan]{name}[/cyan]")
153
+ console.print(f"\n Timestamp: {snapshot['timestamp']}")
154
+ console.print(f" Entries: {snapshot['context_entries']}")
155
+ console.print(f" Summaries: {snapshot['summaries']}")
156
+
157
+
158
+ @app.command("capabilities")
159
+ def show_capabilities():
160
+ """Show all unified capabilities."""
161
+ uacs = get_uacs()
162
+ caps = uacs.get_capabilities()
163
+
164
+ console.print("\n[bold cyan]🎯 Unified Capabilities[/bold cyan]\n")
165
+
166
+ # AGENTS.md
167
+ if caps["agents_md_loaded"]:
168
+ console.print("[green]✓[/green] AGENTS.md loaded")
169
+ project_caps = caps["project_context"]
170
+ if project_caps.get("setup"):
171
+ console.print(f" Setup commands: {len(project_caps['setup'])}")
172
+ if project_caps.get("code_style"):
173
+ console.print(f" Style rules: {len(project_caps['code_style'])}")
174
+ else:
175
+ console.print("[dim]○ AGENTS.md not found[/dim]")
176
+
177
+ # Agent Skills
178
+ skills = caps["available_skills"]
179
+ if skills:
180
+ console.print(f"\n[green]✓[/green] Agent Skills loaded ({len(skills)} skills)")
181
+ for skill in skills[:5]:
182
+ console.print(f" - {skill}")
183
+ if len(skills) > 5:
184
+ console.print(f" [dim]... and {len(skills) - 5} more[/dim]")
185
+ else:
186
+ console.print("\n[dim]○ No skills loaded[/dim]")
187
+
188
+ # Shared Context
189
+ console.print("\n[green]✓[/green] Shared Context active")
190
+ context_stats = caps["shared_context_stats"]
191
+ console.print(f" Entries: {context_stats['entry_count']}")
192
+ console.print(f" Summaries: {context_stats['summary_count']}")
193
+ console.print(f" Compression: {context_stats['compression_ratio']}")
194
+
195
+
196
+ @app.command("clear")
197
+ def clear_context(
198
+ confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
199
+ ):
200
+ """Clear all shared context (keeps Agent Skills and AGENTS.md)."""
201
+ if not confirm:
202
+ response = typer.confirm("Clear all shared context? This cannot be undone.")
203
+ if not response:
204
+ console.print("[yellow]Cancelled[/yellow]")
205
+ return
206
+
207
+ uacs = get_uacs()
208
+
209
+ # Clear context
210
+ uacs.shared_context.entries.clear()
211
+ uacs.shared_context.summaries.clear()
212
+ uacs.shared_context.dedup_index.clear()
213
+
214
+ # Clear storage
215
+ for file in uacs.shared_context.storage_path.glob("*"):
216
+ if file.is_file():
217
+ file.unlink()
218
+
219
+ console.print("[green]✓[/green] Cleared all shared context")
220
+
221
+
222
+ @app.command("validate")
223
+ def validate_project(
224
+ fix: bool = typer.Option(False, "--fix", help="Show fix suggestions"),
225
+ verbose: bool = typer.Option(
226
+ False, "--verbose", "-v", help="Show all issues including suggestions"
227
+ ),
228
+ ):
229
+ """Validate AGENTS.md and agent skills configuration."""
230
+ # Note: ProjectValidator is not part of UACS core, so this command may need updates
231
+ # or be removed if the validator doesn't exist in UACS
232
+ console.print("[yellow]⚠ This command requires ProjectValidator which may not be available in UACS[/yellow]")
233
+ console.print("Use 'uacs skills validate' to validate individual SKILL.md files")
234
+
235
+
236
+ @app.command("build")
237
+ def build_focused_context(
238
+ query: str = typer.Argument(..., help="Query or task for context building"),
239
+ agent: str = typer.Option("claude", "--agent", "-a", help="Agent name"),
240
+ topics: str | None = typer.Option(
241
+ None, "--topics", "-t", help="Comma-separated topics to filter context"
242
+ ),
243
+ max_tokens: int = typer.Option(
244
+ 4000, "--max-tokens", "-m", help="Maximum tokens to return"
245
+ ),
246
+ ):
247
+ """Build focused context filtered by topics."""
248
+ uacs = get_uacs()
249
+
250
+ # Parse topics if provided
251
+ topic_list = [t.strip() for t in topics.split(",")] if topics else None
252
+
253
+ console.print(f"\n[cyan]🔍 Building context for: {query}[/cyan]")
254
+ if topic_list:
255
+ console.print(f"[dim]Topics: {', '.join(topic_list)}[/dim]")
256
+ console.print()
257
+
258
+ # Build context
259
+ context = uacs.build_context(
260
+ query=query, agent=agent, max_tokens=max_tokens, topics=topic_list
261
+ )
262
+
263
+ # Display context
264
+ console.print(Panel(context, title=f"Context for {agent}", border_style="cyan"))
265
+
266
+ # Show token count
267
+ token_count = uacs.shared_context.count_tokens(context)
268
+ console.print(f"\n[dim]Token count: {token_count:,}[/dim]")
269
+
270
+
271
+ @app.command("add")
272
+ def add_context_entry(
273
+ content: str = typer.Argument(..., help="Content to add to context"),
274
+ agent: str = typer.Option("user", "--agent", "-a", help="Agent name"),
275
+ topics: str | None = typer.Option(
276
+ None, "--topics", "-t", help="Comma-separated topics for this entry"
277
+ ),
278
+ ):
279
+ """Add an entry to shared context with optional topics."""
280
+ uacs = get_uacs()
281
+
282
+ # Parse topics if provided
283
+ topic_list = [t.strip() for t in topics.split(",")] if topics else None
284
+
285
+ entry_id = uacs.shared_context.add_entry(
286
+ content=content, agent=agent, topics=topic_list
287
+ )
288
+
289
+ console.print(f"[green]✓[/green] Added context entry: [cyan]{entry_id}[/cyan]")
290
+ if topic_list:
291
+ console.print(f"[dim]Topics: {', '.join(topic_list)}[/dim]")
292
+
293
+
294
+ @app.command("init")
295
+ def init_agents_md():
296
+ """Initialize AGENTS.md file with template."""
297
+ target = get_project_root() / "AGENTS.md"
298
+
299
+ if target.exists():
300
+ console.print(f"[yellow]AGENTS.md already exists: {target}[/yellow]")
301
+ return
302
+
303
+ template = """# AGENTS.md
304
+
305
+ ## Project Overview
306
+ Brief description of your project, its architecture, and key concepts.
307
+
308
+ ## Setup Commands
309
+ - Install dependencies: `npm install` or `pip install -r requirements.txt`
310
+ - Start dev server: `npm run dev` or `python app.py`
311
+
312
+ ## Dev Environment Tips
313
+ - Use environment variables from .env.example
314
+ - Database migrations: `npm run migrate`
315
+ - Check logs: `tail -f logs/app.log`
316
+
317
+ ## Code Style
318
+ - TypeScript strict mode enabled
319
+ - Use single quotes for strings
320
+ - 2-space indentation
321
+ - No semicolons
322
+ - Prefer functional patterns
323
+
324
+ ## Build Commands
325
+ - Build: `npm run build`
326
+ - Test: `npm test`
327
+ - Lint: `npm run lint`
328
+
329
+ ## Testing Instructions
330
+ - Run unit tests: `npm test`
331
+ - Run integration tests: `npm run test:integration`
332
+ - Coverage report: `npm run test:coverage`
333
+ - All tests must pass before merging
334
+
335
+ ## PR Instructions
336
+ - Title format: `[Component] Brief description`
337
+ - Link related issues
338
+ - Update tests for changed code
339
+ - Run `npm run lint` before committing
340
+ - Request review from @team
341
+ """
342
+
343
+ target.write_text(template)
344
+ console.print(f"[green]✓[/green] Created AGENTS.md at {target}")
345
+ console.print("\nEdit this file to customize for your project.")
346
+
347
+
348
+ if __name__ == "__main__":
349
+ app()
uacs/cli/main.py ADDED
@@ -0,0 +1,195 @@
1
+ """UACS CLI - Universal Agent Context System command-line interface."""
2
+
3
+ import asyncio
4
+ from pathlib import Path
5
+
6
+ import typer
7
+
8
+ from uacs.cli import context, memory, mcp, packages, skills
9
+
10
+ app = typer.Typer(
11
+ name="uacs",
12
+ help="Universal Agent Context System - unified context for AI agents",
13
+ no_args_is_help=True,
14
+ )
15
+
16
+ # Register sub-apps
17
+ app.add_typer(skills.app, name="skills")
18
+ app.add_typer(context.app, name="context")
19
+ app.add_typer(packages.app, name="packages")
20
+ app.add_typer(memory.app, name="memory")
21
+ app.add_typer(mcp.app, name="mcp")
22
+
23
+
24
+ @app.command()
25
+ def serve(
26
+ host: str = typer.Option("localhost", "--host", "-h", help="Server host"),
27
+ port: int = typer.Option(8080, "--port", "-p", help="Server port"),
28
+ with_ui: bool = typer.Option(False, "--with-ui", help="Start web UI visualization server"),
29
+ ui_port: int = typer.Option(8081, "--ui-port", help="Web UI port"),
30
+ ):
31
+ """Start UACS MCP server for tool integration.
32
+
33
+ The MCP server exposes all UACS capabilities as tools that can be
34
+ consumed by AI agents via the Model Context Protocol.
35
+
36
+ Use --with-ui to also start the web-based context visualization UI.
37
+
38
+ Examples:
39
+ uacs serve --host 0.0.0.0 --port 8080
40
+ uacs serve --with-ui --ui-port 8081
41
+ """
42
+ from uacs.protocols.mcp.skills_server import main as mcp_main
43
+
44
+ console = typer.get_text_stream("stdout")
45
+ typer.echo(f"Starting UACS MCP server on {host}:{port}...")
46
+ typer.echo("Exposing skills, context, and package management tools")
47
+
48
+ if with_ui:
49
+ typer.echo(f"Web UI will be available at http://{host}:{ui_port}")
50
+ typer.echo("Starting visualization server...")
51
+ _run_with_ui(host, port, ui_port)
52
+ else:
53
+ typer.echo("Press Ctrl+C to stop\n")
54
+ try:
55
+ asyncio.run(mcp_main())
56
+ except KeyboardInterrupt:
57
+ typer.echo("\n\nServer stopped")
58
+
59
+
60
+ def _run_with_ui(host: str, port: int, ui_port: int):
61
+ """Run MCP server with web UI visualization.
62
+
63
+ Args:
64
+ host: Server host
65
+ port: MCP server port
66
+ ui_port: Web UI port
67
+ """
68
+ import uvicorn
69
+ from pathlib import Path
70
+ from uacs.context.shared_context import SharedContextManager
71
+ from uacs.visualization.web_server import VisualizationServer
72
+
73
+ # Initialize shared context manager
74
+ storage_path = Path.cwd() / ".state" / "context"
75
+ context_manager = SharedContextManager(storage_path=storage_path)
76
+
77
+ # Create visualization server
78
+ viz_server = VisualizationServer(context_manager, host, ui_port)
79
+
80
+ # Print startup message
81
+ typer.echo(f"\n✓ Web UI available at http://{host}:{ui_port}")
82
+ typer.echo("✓ MCP server running (stdio mode)")
83
+ typer.echo("Press Ctrl+C to stop\n")
84
+
85
+ # Run visualization server (MCP server runs in stdio mode alongside)
86
+ config = uvicorn.Config(
87
+ viz_server.app,
88
+ host=host,
89
+ port=ui_port,
90
+ log_level="info",
91
+ )
92
+ server = uvicorn.Server(config)
93
+
94
+ try:
95
+ asyncio.run(server.serve())
96
+ except KeyboardInterrupt:
97
+ typer.echo("\n\nServers stopped")
98
+
99
+
100
+ @app.command()
101
+ def version():
102
+ """Show UACS version information."""
103
+ try:
104
+ from importlib.metadata import version as get_version
105
+
106
+ uacs_version = get_version("universal-agent-context")
107
+ typer.echo(f"UACS version: {uacs_version}")
108
+ except Exception:
109
+ typer.echo("UACS version: development")
110
+
111
+
112
+ @app.command()
113
+ def init(
114
+ project_root: Path = typer.Argument(
115
+ Path.cwd(), help="Project root directory (default: current directory)"
116
+ ),
117
+ ):
118
+ """Initialize UACS for a project.
119
+
120
+ Creates necessary directories and example configuration files.
121
+ """
122
+ from rich.console import Console
123
+
124
+ console = Console()
125
+
126
+ # Create .agent directory structure
127
+ agent_dir = project_root / ".agent"
128
+ skills_dir = agent_dir / "skills"
129
+ state_dir = project_root / ".state"
130
+ context_dir = state_dir / "context"
131
+
132
+ dirs_to_create = [agent_dir, skills_dir, state_dir, context_dir]
133
+
134
+ for directory in dirs_to_create:
135
+ if not directory.exists():
136
+ directory.mkdir(parents=True, exist_ok=True)
137
+ console.print(f"[green]✓[/green] Created {directory}")
138
+ else:
139
+ console.print(f"[dim]○[/dim] Already exists: {directory}")
140
+
141
+ # Create example Agent Skill if .agent/skills/ is empty
142
+ skills_dir = project_root / ".agent" / "skills"
143
+ example_skill_dir = skills_dir / "example-skill"
144
+ if skills_dir.exists() and not any(skills_dir.iterdir()):
145
+ # Directory exists but is empty - create example
146
+ example_skill_dir.mkdir(parents=True, exist_ok=True)
147
+ example_skill = """---
148
+ name: example-skill
149
+ description: Example skill demonstrating the Agent Skills format
150
+ ---
151
+
152
+ # Example Skill
153
+
154
+ This is an example skill showing the Agent Skills format structure.
155
+
156
+ ## When to Use
157
+
158
+ Use this skill when you need to demonstrate:
159
+ - How to structure a skill with YAML frontmatter
160
+ - How to organize instructions
161
+ - How to trigger skill usage
162
+
163
+ ## Instructions
164
+
165
+ 1. **Understand the format**: Skills use YAML frontmatter + Markdown body
166
+ 2. **Define clear triggers**: Describe when this skill should be used
167
+ 3. **Provide actionable steps**: Break down the skill into clear instructions
168
+ 4. **Include examples**: Show concrete usage examples when relevant
169
+
170
+ ## Examples
171
+
172
+ When a user asks "Show me how skills work", you can:
173
+ 1. Reference this example skill
174
+ 2. Explain the YAML frontmatter structure
175
+ 3. Show the markdown instruction format
176
+ """
177
+ skill_file = example_skill_dir / "SKILL.md"
178
+ skill_file.write_text(example_skill)
179
+ console.print(
180
+ f"[green]✓[/green] Created example skill {example_skill_dir.name}"
181
+ )
182
+
183
+ console.print("\n[bold cyan]UACS initialized successfully![/bold cyan]")
184
+ console.print("\nNext steps:")
185
+ console.print(" 1. Run 'uacs skills list' to see available skills")
186
+ console.print(" 2. Run 'uacs install owner/repo' to install packages from GitHub")
187
+ console.print(" 3. Run 'uacs list' to see installed packages")
188
+ console.print(" 4. Run 'uacs serve' to start the MCP server")
189
+
190
+
191
+ __all__ = ["app"]
192
+
193
+
194
+ if __name__ == "__main__":
195
+ app()
uacs/cli/mcp.py ADDED
@@ -0,0 +1,115 @@
1
+ """CLI commands for MCP server management."""
2
+
3
+ import json
4
+
5
+ import typer
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+
9
+ from uacs.protocols.mcp.manager import McpManager
10
+
11
+ app = typer.Typer(help="Manage MCP servers")
12
+ console = Console()
13
+
14
+
15
+ @app.command("list")
16
+ def list_servers():
17
+ """List all MCP servers in use in this project, regardless of installation source."""
18
+ manager = McpManager()
19
+ servers = manager.list_servers()
20
+
21
+ if not servers:
22
+ console.print("[yellow]No MCP servers configured.[/yellow]")
23
+ console.print("\nConfigure MCP servers in: ~/.uacs/mcp-config.json")
24
+ return
25
+
26
+ table = Table(title="MCP Servers in Use")
27
+ table.add_column("Name", style="cyan", width=20)
28
+ table.add_column("Command", style="green", width=40)
29
+ table.add_column("Args", style="white", width=30)
30
+ table.add_column("Status", style="magenta", width=10)
31
+
32
+ # Add configured servers
33
+ for server in servers:
34
+ status = "Enabled" if server.enabled else "Disabled"
35
+ args_str = " ".join(server.args[:2])
36
+ if len(server.args) > 2:
37
+ args_str += "..."
38
+
39
+ table.add_row(server.name, server.command, args_str, status)
40
+
41
+ console.print(table)
42
+ console.print(f"\n[dim]Total: {len(servers)} MCP server(s) configured[/dim]")
43
+
44
+
45
+ @app.command("add")
46
+ def add_server(
47
+ name: str = typer.Argument(..., help="Name of the MCP server"),
48
+ command: str = typer.Argument(..., help="Executable command"),
49
+ args: list[str] = typer.Argument(None, help="Arguments for the command"),
50
+ env: str = typer.Option(None, help="Environment variables as JSON string"),
51
+ ):
52
+ """Add a new MCP server."""
53
+ manager = McpManager()
54
+
55
+ env_dict = {}
56
+ if env:
57
+ try:
58
+ env_dict = json.loads(env)
59
+ except json.JSONDecodeError:
60
+ console.print("[red]Error: Invalid JSON for environment variables[/red]")
61
+ raise typer.Exit(1)
62
+
63
+ manager.add_server(name, command, args or [], env_dict)
64
+ console.print(f"[green]Added MCP server: {name}[/green]")
65
+
66
+
67
+ @app.command("remove")
68
+ def remove_server(name: str = typer.Argument(..., help="Name of the MCP server")):
69
+ """Remove an MCP server."""
70
+ manager = McpManager()
71
+ if manager.get_server(name):
72
+ manager.remove_server(name)
73
+ console.print(f"[green]Removed MCP server: {name}[/green]")
74
+ else:
75
+ console.print(f"[red]Server {name} not found[/red]")
76
+
77
+
78
+ @app.command("install-filesystem")
79
+ def install_filesystem(
80
+ path: str = typer.Argument(..., help="Root path for filesystem access"),
81
+ ):
82
+ """Helper to add the standard filesystem MCP server."""
83
+ manager = McpManager()
84
+ import shutil
85
+
86
+ # Detect available runner
87
+ command = None
88
+ args = []
89
+
90
+ if shutil.which("pnpm"):
91
+ command = "pnpm"
92
+ args = ["dlx", "@modelcontextprotocol/server-filesystem", path]
93
+ elif shutil.which("bun"):
94
+ command = "bun"
95
+ args = ["x", "@modelcontextprotocol/server-filesystem", path]
96
+ elif shutil.which("npx"):
97
+ command = "npx"
98
+ args = ["-y", "@modelcontextprotocol/server-filesystem", path]
99
+ else:
100
+ console.print(
101
+ "[red]Error: No suitable runner found (pnpm, bun, or npx required).[/red]"
102
+ )
103
+ raise typer.Exit(1)
104
+
105
+ manager.add_server(name="filesystem", command=command, args=args)
106
+ console.print(
107
+ f"[green]Added filesystem MCP server using {command} for path: {path}[/green]"
108
+ )
109
+
110
+
111
+ __all__ = ["app"]
112
+
113
+
114
+ if __name__ == "__main__":
115
+ app()