mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-19 10:33:17 +08:00
初始化:独立 oclaw 仓库首提交
- 在 oclaw/ 下重新初始化 Git 仓库 - 补齐子仓库 .gitignore,避免提交本地运行态数据(_local、node_modules、logs 等) - 提交当前工程代码与配置 Made-with: Cursor
This commit is contained in:
commit
ba3836f00f
579 changed files with 83112 additions and 0 deletions
153
tools/mcp/market.py
Normal file
153
tools/mcp/market.py
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
import time
|
||||
|
||||
import httpx
|
||||
|
||||
_TRENDING_CACHE: dict[str, Any] = {"ts": 0.0, "items": []}
|
||||
_TRENDING_TTL_S = 1800
|
||||
|
||||
|
||||
def _safe_get_json(url: str, *, params: dict[str, Any] | None = None, headers: dict[str, str] | None = None) -> dict[str, Any]:
|
||||
try:
|
||||
with httpx.Client(timeout=8.0, follow_redirects=True) as c:
|
||||
r = c.get(url, params=params or {}, headers=headers or {})
|
||||
if r.status_code != 200:
|
||||
return {}
|
||||
obj = r.json()
|
||||
return obj if isinstance(obj, dict) else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def search_github_repos(query: str, *, limit: int = 8) -> list[dict[str, Any]]:
|
||||
q = str(query or "").strip()
|
||||
if not q:
|
||||
return []
|
||||
blob = _safe_get_json(
|
||||
"https://api.github.com/search/repositories",
|
||||
params={"q": f"{q} mcp server", "sort": "stars", "order": "desc", "per_page": max(1, min(limit, 20))},
|
||||
headers={"Accept": "application/vnd.github+json"},
|
||||
)
|
||||
items = blob.get("items") if isinstance(blob.get("items"), list) else []
|
||||
out: list[dict[str, Any]] = []
|
||||
for it in items[:limit]:
|
||||
if not isinstance(it, dict):
|
||||
continue
|
||||
out.append(
|
||||
{
|
||||
"source_type": "github",
|
||||
"name": str(it.get("full_name") or ""),
|
||||
"source_ref": str(it.get("clone_url") or it.get("html_url") or ""),
|
||||
"description": str(it.get("description") or ""),
|
||||
"version": "",
|
||||
"homepage": str(it.get("html_url") or ""),
|
||||
"stars": int(it.get("stargazers_count") or 0),
|
||||
"install_template": infer_install_template("github", str(it.get("clone_url") or it.get("html_url") or "")),
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def search_npm_packages(query: str, *, limit: int = 8) -> list[dict[str, Any]]:
|
||||
q = str(query or "").strip()
|
||||
if not q:
|
||||
return []
|
||||
blob = _safe_get_json(
|
||||
"https://registry.npmjs.org/-/v1/search",
|
||||
params={"text": f"{q} mcp", "size": max(1, min(limit, 20))},
|
||||
)
|
||||
items = blob.get("objects") if isinstance(blob.get("objects"), list) else []
|
||||
out: list[dict[str, Any]] = []
|
||||
for it in items[:limit]:
|
||||
pkg = it.get("package") if isinstance(it, dict) else None
|
||||
if not isinstance(pkg, dict):
|
||||
continue
|
||||
out.append(
|
||||
{
|
||||
"source_type": "npm",
|
||||
"name": str(pkg.get("name") or ""),
|
||||
"source_ref": str(pkg.get("name") or ""),
|
||||
"description": str(pkg.get("description") or ""),
|
||||
"version": str(pkg.get("version") or ""),
|
||||
"homepage": str(pkg.get("links", {}).get("npm") if isinstance(pkg.get("links"), dict) else ""),
|
||||
"stars": 0,
|
||||
"install_template": infer_install_template("npm", str(pkg.get("name") or "")),
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def search_pypi_packages(query: str, *, limit: int = 8) -> list[dict[str, Any]]:
|
||||
q = str(query or "").strip()
|
||||
if not q:
|
||||
return []
|
||||
blob = _safe_get_json(
|
||||
"https://pypi.org/search/",
|
||||
params={"q": f"{q} mcp"},
|
||||
headers={"Accept": "application/json"},
|
||||
)
|
||||
# PyPI JSON search API is not officially stable; keep best-effort.
|
||||
projects = blob.get("projects") if isinstance(blob.get("projects"), list) else []
|
||||
out: list[dict[str, Any]] = []
|
||||
for it in projects[:limit]:
|
||||
if not isinstance(it, dict):
|
||||
continue
|
||||
name = str(it.get("name") or "")
|
||||
out.append(
|
||||
{
|
||||
"source_type": "pypi",
|
||||
"name": name,
|
||||
"source_ref": name,
|
||||
"description": str(it.get("description") or ""),
|
||||
"version": str(it.get("version") or ""),
|
||||
"homepage": f"https://pypi.org/project/{name}/" if name else "",
|
||||
"stars": 0,
|
||||
"install_template": infer_install_template("pypi", name),
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def search_mcp_market(query: str, *, per_source_limit: int = 6) -> list[dict[str, Any]]:
|
||||
lim = max(1, min(int(per_source_limit or 6), 20))
|
||||
out: list[dict[str, Any]] = []
|
||||
out.extend(search_github_repos(query, limit=lim))
|
||||
out.extend(search_npm_packages(query, limit=lim))
|
||||
out.extend(search_pypi_packages(query, limit=lim))
|
||||
return out
|
||||
|
||||
|
||||
def infer_install_template(source_type: str, source_ref: str) -> dict[str, Any]:
|
||||
st = str(source_type or "").strip().lower()
|
||||
sr = str(source_ref or "").strip()
|
||||
if st == "npm":
|
||||
pkg = sr.split("/")[-1] if sr else ""
|
||||
return {"entry_command": "npx", "entry_args": [pkg] if pkg else []}
|
||||
if st == "pypi":
|
||||
pkg = sr.replace("-", "_")
|
||||
return {"entry_command": "python", "entry_args": ["-m", pkg] if pkg else []}
|
||||
return {"entry_command": "python", "entry_args": []}
|
||||
|
||||
|
||||
def trending_mcp_market(*, force_refresh: bool = False, per_source_limit: int = 5) -> list[dict[str, Any]]:
|
||||
now = time.time()
|
||||
if not force_refresh and _TRENDING_CACHE["items"] and (now - float(_TRENDING_CACHE["ts"] or 0.0) < _TRENDING_TTL_S):
|
||||
return list(_TRENDING_CACHE["items"])
|
||||
items = search_mcp_market("mcp", per_source_limit=per_source_limit)
|
||||
items = sorted(items, key=lambda x: int(x.get("stars") or 0), reverse=True)
|
||||
_TRENDING_CACHE["ts"] = now
|
||||
_TRENDING_CACHE["items"] = list(items)
|
||||
return items
|
||||
|
||||
|
||||
__all__ = [
|
||||
"search_mcp_market",
|
||||
"search_github_repos",
|
||||
"search_npm_packages",
|
||||
"search_pypi_packages",
|
||||
"infer_install_template",
|
||||
"trending_mcp_market",
|
||||
]
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue