Coverage for agentos/server/marketplace_server.py: 0%
70 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-02 09:59 +0800
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-02 09:59 +0800
1"""
2AgentOS Skill Marketplace Server (v1.8.1).
4FastAPI server serving:
5 - /api/skills/installed — list all 64+ installed skills
6 - /api/skills/search — search by name/description/tag
7 - /api/skills/{name} — get skill detail
8 - /api/ecosystems — external ecosystem links
9 - / — marketplace web UI (static page)
11Also serves built-in MCP info: /api/mcp/servers, /api/mcp/tools
12"""
14from __future__ import annotations
16import json
17import sys
18from pathlib import Path
19from typing import Optional
21# ── FastAPI app ──
22try:
23 from fastapi import FastAPI, Query, HTTPException
24 from fastapi.responses import HTMLResponse, JSONResponse
25 from fastapi.staticfiles import StaticFiles
26 from fastapi.middleware.cors import CORSMiddleware
27except ImportError:
28 FastAPI = object # type: ignore
30from agentos.marketplace.registry import SkillRegistry
32STATIC_DIR = Path(__file__).parent / "static"
35def create_marketplace_app() -> "FastAPI":
36 """Create and configure the marketplace FastAPI application."""
38 if FastAPI is object:
39 raise RuntimeError("FastAPI not installed. Run: pip install fastapi uvicorn")
41 app = FastAPI(
42 title="AgentOS Skill Marketplace",
43 version="1.8.1",
44 description="Browse, search, and install skills. Compatible with OpenClaw/MCP ecosystem.",
45 )
47 app.add_middleware(
48 CORSMiddleware,
49 allow_origins=["*"],
50 allow_credentials=True,
51 allow_methods=["*"],
52 allow_headers=["*"],
53 )
55 registry = SkillRegistry()
57 # ── Static files ─────────────────────────
59 if STATIC_DIR.exists():
60 app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
62 @app.get("/", response_class=HTMLResponse)
63 async def index():
64 """Serve marketplace web UI."""
65 html_path = STATIC_DIR / "marketplace.html"
66 if html_path.exists():
67 content = html_path.read_text(encoding="utf-8")
68 return HTMLResponse(content)
69 return HTMLResponse("<h1>Marketplace UI not found</h1>", status_code=404)
71 # ── Skill APIs ───────────────────────────
73 @app.get("/api/skills/installed")
74 async def list_installed():
75 """List all installed skills with metadata."""
76 skills = registry.list_installed()
77 return {
78 "count": len(skills),
79 "skills": [
80 {
81 "name": s.name,
82 "version": s.version,
83 "description": s.description,
84 "author": s.author,
85 "tags": s.tags,
86 "category": s.category,
87 "source": s.source,
88 "format": s.format,
89 "entrypoint": s.entrypoint,
90 "installed_at": getattr(s, "installed_at", None),
91 }
92 for s in skills
93 ],
94 }
96 @app.get("/api/skills/search")
97 async def search_skills(q: str = Query("", description="Search query"), limit: int = Query(50)):
98 """Search installed skills by name/description/tag."""
99 skills = registry.list_installed()
100 q_lower = q.lower()
101 results = []
102 for s in skills:
103 if (q_lower in (s.name or "").lower() or
104 q_lower in (s.description or "").lower() or
105 any(q_lower in (t or "").lower() for t in (s.tags or []))):
106 results.append({
107 "name": s.name, "version": s.version, "description": s.description,
108 "tags": s.tags, "category": s.category,
109 })
110 if len(results) >= limit:
111 break
112 return {"query": q, "count": len(results), "results": results}
114 @app.get("/api/skills/{name}")
115 async def get_skill(name: str):
116 """Get detailed info for a specific skill."""
117 skill = registry.get_installed(name)
118 if not skill:
119 raise HTTPException(status_code=404, detail=f"Skill '{name}' not found")
120 return {
121 "name": skill.name,
122 "version": skill.version,
123 "description": skill.description,
124 "author": skill.author,
125 "tags": skill.tags,
126 "category": skill.category,
127 "source": skill.source,
128 "format": skill.format,
129 "entrypoint": skill.entrypoint,
130 "tools": [t.to_dict() if hasattr(t, "to_dict") else t for t in (skill.tools or [])],
131 "dependencies": skill.dependencies,
132 "installed_at": getattr(skill, "installed_at", None),
133 }
135 # ── Ecosystem API ────────────────────────
137 @app.get("/api/ecosystems")
138 async def list_ecosystems():
139 """List external skill ecosystems compatible with AgentOS."""
140 return {
141 "ecosystems": [
142 {
143 "name": "OpenClaw Skill Store",
144 "url": "https://github.com/nicepkg/openclaw-skill-store",
145 "skill_count": "13,700+",
146 "format": "openclaw",
147 "description": "Largest community-driven skill ecosystem",
148 "badge": "github",
149 },
150 {
151 "name": "ClawHub",
152 "url": "https://clawhub.eu.org/",
153 "skill_count": "curated",
154 "format": "openclaw",
155 "description": "Curated high-quality OpenClaw skills",
156 },
157 {
158 "name": "Skills Marketplace",
159 "url": "https://skills.sh/",
160 "skill_count": "multi-framework",
161 "format": "openclaw/agentos",
162 "description": "Cross-framework skill discovery platform",
163 },
164 {
165 "name": "MCP Servers",
166 "url": "https://github.com/modelcontextprotocol/servers",
167 "skill_count": "2,000+",
168 "format": "mcp",
169 "description": "Official MCP server registry",
170 },
171 {
172 "name": "LobeHub Plugins",
173 "url": "https://lobehub.com/plugins",
174 "skill_count": "356+",
175 "format": "lobehub",
176 "description": "LobeChat plugin ecosystem, adaptable to AgentOS",
177 },
178 {
179 "name": "Awesome Agent Skills",
180 "url": "https://github.com/topics/agent-skills",
181 "skill_count": "curated",
182 "format": "multi",
183 "description": "Community curated list of agent skills",
184 },
185 ]
186 }
188 # ── MCP Info API ─────────────────────────
190 @app.get("/api/mcp/servers")
191 async def mcp_servers():
192 """List built-in MCP servers and their tools."""
193 try:
194 from agentos.mcp.builtin_servers import create_default_registry
195 reg = create_default_registry()
196 return {
197 "servers": [
198 {
199 "name": name,
200 "tool_count": len(reg._servers[name].get_tools()),
201 "tools": [
202 {"name": t["name"], "description": t["description"]}
203 for t in reg._servers[name].get_tools()
204 ],
205 }
206 for name in reg.server_names
207 ],
208 "total_tools": reg.tool_count,
209 }
210 except Exception as e:
211 return {"error": str(e), "servers": []}
213 return app
216def start_marketplace_server(host: str = "0.0.0.0", port: int = 8910) -> None:
217 """Start the marketplace server (blocking)."""
218 import uvicorn
219 app = create_marketplace_app()
220 print(f"\n AgentOS Skill Marketplace")
221 print(f" Local: http://{host}:{port}")
222 print(f" Skills: {len(SkillRegistry().list_installed())} installed")
223 print()
224 uvicorn.run(app, host=host, port=port, log_level="info")