Coverage for agentos/server/marketplace_server.py: 0%

67 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-06 10:59 +0800

1""" 

2AgentOS Skill Marketplace Server (v1.8.1). 

3 

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) 

10 

11Also serves built-in MCP info: /api/mcp/servers, /api/mcp/tools 

12""" 

13 

14from __future__ import annotations 

15 

16from pathlib import Path 

17 

18# ── FastAPI app ── 

19try: 

20 from fastapi import FastAPI, Query, HTTPException 

21 from fastapi.responses import HTMLResponse, JSONResponse 

22 from fastapi.staticfiles import StaticFiles 

23 from fastapi.middleware.cors import CORSMiddleware 

24except ImportError: 

25 FastAPI = object # type: ignore 

26 

27from agentos.marketplace.registry import SkillRegistry 

28 

29STATIC_DIR = Path(__file__).parent / "static" 

30 

31 

32def create_marketplace_app() -> "FastAPI": 

33 """Create and configure the marketplace FastAPI application.""" 

34 

35 if FastAPI is object: 

36 raise RuntimeError("FastAPI not installed. Run: pip install fastapi uvicorn") 

37 

38 app = FastAPI( 

39 title="AgentOS Skill Marketplace", 

40 version="1.8.1", 

41 description="Browse, search, and install skills. Compatible with OpenClaw/MCP ecosystem.", 

42 ) 

43 

44 app.add_middleware( 

45 CORSMiddleware, 

46 allow_origins=["*"], 

47 allow_credentials=True, 

48 allow_methods=["*"], 

49 allow_headers=["*"], 

50 ) 

51 

52 registry = SkillRegistry() 

53 

54 # ── Static files ───────────────────────── 

55 

56 if STATIC_DIR.exists(): 

57 app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static") 

58 

59 @app.get("/", response_class=HTMLResponse) 

60 async def index(): 

61 """Serve marketplace web UI.""" 

62 html_path = STATIC_DIR / "marketplace.html" 

63 if html_path.exists(): 

64 content = html_path.read_text(encoding="utf-8") 

65 return HTMLResponse(content) 

66 return HTMLResponse("<h1>Marketplace UI not found</h1>", status_code=404) 

67 

68 # ── Skill APIs ─────────────────────────── 

69 

70 @app.get("/api/skills/installed") 

71 async def list_installed(): 

72 """List all installed skills with metadata.""" 

73 skills = registry.list_installed() 

74 return { 

75 "count": len(skills), 

76 "skills": [ 

77 { 

78 "name": s.name, 

79 "version": s.version, 

80 "description": s.description, 

81 "author": s.author, 

82 "tags": s.tags, 

83 "category": s.category, 

84 "source": s.source, 

85 "format": s.format, 

86 "entrypoint": s.entrypoint, 

87 "installed_at": getattr(s, "installed_at", None), 

88 } 

89 for s in skills 

90 ], 

91 } 

92 

93 @app.get("/api/skills/search") 

94 async def search_skills(q: str = Query("", description="Search query"), limit: int = Query(50)): 

95 """Search installed skills by name/description/tag.""" 

96 skills = registry.list_installed() 

97 q_lower = q.lower() 

98 results = [] 

99 for s in skills: 

100 if (q_lower in (s.name or "").lower() or 

101 q_lower in (s.description or "").lower() or 

102 any(q_lower in (t or "").lower() for t in (s.tags or []))): 

103 results.append({ 

104 "name": s.name, "version": s.version, "description": s.description, 

105 "tags": s.tags, "category": s.category, 

106 }) 

107 if len(results) >= limit: 

108 break 

109 return {"query": q, "count": len(results), "results": results} 

110 

111 @app.get("/api/skills/{name}") 

112 async def get_skill(name: str): 

113 """Get detailed info for a specific skill.""" 

114 skill = registry.get_installed(name) 

115 if not skill: 

116 raise HTTPException(status_code=404, detail=f"Skill '{name}' not found") 

117 return { 

118 "name": skill.name, 

119 "version": skill.version, 

120 "description": skill.description, 

121 "author": skill.author, 

122 "tags": skill.tags, 

123 "category": skill.category, 

124 "source": skill.source, 

125 "format": skill.format, 

126 "entrypoint": skill.entrypoint, 

127 "tools": [t.to_dict() if hasattr(t, "to_dict") else t for t in (skill.tools or [])], 

128 "dependencies": skill.dependencies, 

129 "installed_at": getattr(skill, "installed_at", None), 

130 } 

131 

132 # ── Ecosystem API ──────────────────────── 

133 

134 @app.get("/api/ecosystems") 

135 async def list_ecosystems(): 

136 """List external skill ecosystems compatible with AgentOS.""" 

137 return { 

138 "ecosystems": [ 

139 { 

140 "name": "OpenClaw Skill Store", 

141 "url": "https://github.com/nicepkg/openclaw-skill-store", 

142 "skill_count": "13,700+", 

143 "format": "openclaw", 

144 "description": "Largest community-driven skill ecosystem", 

145 "badge": "github", 

146 }, 

147 { 

148 "name": "ClawHub", 

149 "url": "https://clawhub.eu.org/", 

150 "skill_count": "curated", 

151 "format": "openclaw", 

152 "description": "Curated high-quality OpenClaw skills", 

153 }, 

154 { 

155 "name": "Skills Marketplace", 

156 "url": "https://skills.sh/", 

157 "skill_count": "multi-framework", 

158 "format": "openclaw/agentos", 

159 "description": "Cross-framework skill discovery platform", 

160 }, 

161 { 

162 "name": "MCP Servers", 

163 "url": "https://github.com/modelcontextprotocol/servers", 

164 "skill_count": "2,000+", 

165 "format": "mcp", 

166 "description": "Official MCP server registry", 

167 }, 

168 { 

169 "name": "LobeHub Plugins", 

170 "url": "https://lobehub.com/plugins", 

171 "skill_count": "356+", 

172 "format": "lobehub", 

173 "description": "LobeChat plugin ecosystem, adaptable to AgentOS", 

174 }, 

175 { 

176 "name": "Awesome Agent Skills", 

177 "url": "https://github.com/topics/agent-skills", 

178 "skill_count": "curated", 

179 "format": "multi", 

180 "description": "Community curated list of agent skills", 

181 }, 

182 ] 

183 } 

184 

185 # ── MCP Info API ───────────────────────── 

186 

187 @app.get("/api/mcp/servers") 

188 async def mcp_servers(): 

189 """List built-in MCP servers and their tools.""" 

190 try: 

191 from agentos.mcp.builtin_servers import create_default_registry 

192 reg = create_default_registry() 

193 return { 

194 "servers": [ 

195 { 

196 "name": name, 

197 "tool_count": len(reg._servers[name].get_tools()), 

198 "tools": [ 

199 {"name": t["name"], "description": t["description"]} 

200 for t in reg._servers[name].get_tools() 

201 ], 

202 } 

203 for name in reg.server_names 

204 ], 

205 "total_tools": reg.tool_count, 

206 } 

207 except Exception as e: 

208 return {"error": str(e), "servers": []} 

209 

210 return app 

211 

212 

213def start_marketplace_server(host: str = "0.0.0.0", port: int = 8910) -> None: 

214 """Start the marketplace server (blocking).""" 

215 import uvicorn 

216 app = create_marketplace_app() 

217 print("\n AgentOS Skill Marketplace") 

218 print(f" Local: http://{host}:{port}") 

219 print(f" Skills: {len(SkillRegistry().list_installed())} installed") 

220 print() 

221 uvicorn.run(app, host=host, port=port, log_level="info")