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

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 

16import json 

17import sys 

18from pathlib import Path 

19from typing import Optional 

20 

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 

29 

30from agentos.marketplace.registry import SkillRegistry 

31 

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

33 

34 

35def create_marketplace_app() -> "FastAPI": 

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

37 

38 if FastAPI is object: 

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

40 

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 ) 

46 

47 app.add_middleware( 

48 CORSMiddleware, 

49 allow_origins=["*"], 

50 allow_credentials=True, 

51 allow_methods=["*"], 

52 allow_headers=["*"], 

53 ) 

54 

55 registry = SkillRegistry() 

56 

57 # ── Static files ───────────────────────── 

58 

59 if STATIC_DIR.exists(): 

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

61 

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) 

70 

71 # ── Skill APIs ─────────────────────────── 

72 

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 } 

95 

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} 

113 

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 } 

134 

135 # ── Ecosystem API ──────────────────────── 

136 

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 } 

187 

188 # ── MCP Info API ───────────────────────── 

189 

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": []} 

212 

213 return app 

214 

215 

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")