1"""

2 AgentOS Skill Marketplace Server (v1.8.1).

3

4 FastAPI 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

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

12"""

13

14 from __future__ import annotations

15

16 import json

17 import sys

18 from pathlib import Path

19 from typing import Optional

20

21# ── FastAPI app ──

22 try:

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

27 except ImportError:

28 FastAPI = object # type: ignore

29

30 from agentos.marketplace.registry import SkillRegistry

31

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

33

34

35 def 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

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