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

67 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-09 10:19 +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, HTTPException, Query 

21 from fastapi.middleware.cors import CORSMiddleware 

22 from fastapi.responses import HTMLResponse 

23 from fastapi.staticfiles import StaticFiles 

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 ( 

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

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

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

104 ): 

105 results.append( 

106 { 

107 "name": s.name, 

108 "version": s.version, 

109 "description": s.description, 

110 "tags": s.tags, 

111 "category": s.category, 

112 } 

113 ) 

114 if len(results) >= limit: 

115 break 

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

117 

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

119 async def get_skill(name: str): 

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

121 skill = registry.get_installed(name) 

122 if not skill: 

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

124 return { 

125 "name": skill.name, 

126 "version": skill.version, 

127 "description": skill.description, 

128 "author": skill.author, 

129 "tags": skill.tags, 

130 "category": skill.category, 

131 "source": skill.source, 

132 "format": skill.format, 

133 "entrypoint": skill.entrypoint, 

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

135 "dependencies": skill.dependencies, 

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

137 } 

138 

139 # ── Ecosystem API ──────────────────────── 

140 

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

142 async def list_ecosystems(): 

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

144 return { 

145 "ecosystems": [ 

146 { 

147 "name": "OpenClaw Skill Store", 

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

149 "skill_count": "13,700+", 

150 "format": "openclaw", 

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

152 "badge": "github", 

153 }, 

154 { 

155 "name": "ClawHub", 

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

157 "skill_count": "curated", 

158 "format": "openclaw", 

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

160 }, 

161 { 

162 "name": "Skills Marketplace", 

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

164 "skill_count": "multi-framework", 

165 "format": "openclaw/agentos", 

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

167 }, 

168 { 

169 "name": "MCP Servers", 

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

171 "skill_count": "2,000+", 

172 "format": "mcp", 

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

174 }, 

175 { 

176 "name": "LobeHub Plugins", 

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

178 "skill_count": "356+", 

179 "format": "lobehub", 

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

181 }, 

182 { 

183 "name": "Awesome Agent Skills", 

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

185 "skill_count": "curated", 

186 "format": "multi", 

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

188 }, 

189 ] 

190 } 

191 

192 # ── MCP Info API ───────────────────────── 

193 

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

195 async def mcp_servers(): 

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

197 try: 

198 from agentos.mcp.builtin_servers import create_default_registry 

199 

200 reg = create_default_registry() 

201 return { 

202 "servers": [ 

203 { 

204 "name": name, 

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

206 "tools": [ 

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

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

209 ], 

210 } 

211 for name in reg.server_names 

212 ], 

213 "total_tools": reg.tool_count, 

214 } 

215 except Exception as e: 

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

217 

218 return app 

219 

220 

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

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

223 import uvicorn 

224 

225 app = create_marketplace_app() 

226 print("\n AgentOS Skill Marketplace") 

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

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

229 print() 

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