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.
- uacs/__init__.py +12 -0
- uacs/adapters/__init__.py +19 -0
- uacs/adapters/agent_skill_adapter.py +202 -0
- uacs/adapters/agents_md_adapter.py +330 -0
- uacs/adapters/base.py +261 -0
- uacs/adapters/clinerules_adapter.py +39 -0
- uacs/adapters/cursorrules_adapter.py +39 -0
- uacs/api.py +262 -0
- uacs/cli/__init__.py +6 -0
- uacs/cli/context.py +349 -0
- uacs/cli/main.py +195 -0
- uacs/cli/mcp.py +115 -0
- uacs/cli/memory.py +142 -0
- uacs/cli/packages.py +309 -0
- uacs/cli/skills.py +144 -0
- uacs/cli/utils.py +24 -0
- uacs/config/repositories.yaml +26 -0
- uacs/context/__init__.py +0 -0
- uacs/context/agent_context.py +406 -0
- uacs/context/shared_context.py +661 -0
- uacs/context/unified_context.py +332 -0
- uacs/mcp_server_entry.py +80 -0
- uacs/memory/__init__.py +5 -0
- uacs/memory/simple_memory.py +255 -0
- uacs/packages/__init__.py +26 -0
- uacs/packages/manager.py +413 -0
- uacs/packages/models.py +60 -0
- uacs/packages/sources.py +270 -0
- uacs/protocols/__init__.py +5 -0
- uacs/protocols/mcp/__init__.py +8 -0
- uacs/protocols/mcp/manager.py +77 -0
- uacs/protocols/mcp/skills_server.py +700 -0
- uacs/skills_validator.py +367 -0
- uacs/utils/__init__.py +5 -0
- uacs/utils/paths.py +24 -0
- uacs/visualization/README.md +132 -0
- uacs/visualization/__init__.py +36 -0
- uacs/visualization/models.py +195 -0
- uacs/visualization/static/index.html +857 -0
- uacs/visualization/storage.py +402 -0
- uacs/visualization/visualization.py +328 -0
- uacs/visualization/web_server.py +364 -0
- universal_agent_context-0.2.0.dist-info/METADATA +873 -0
- universal_agent_context-0.2.0.dist-info/RECORD +47 -0
- universal_agent_context-0.2.0.dist-info/WHEEL +4 -0
- universal_agent_context-0.2.0.dist-info/entry_points.txt +2 -0
- 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()
|