Add some more MCP tools
One for listing the repos, and one for searching packages
This commit is contained in:
parent
1c703c66d7
commit
809daefa3f
@ -813,3 +813,45 @@ class SrcInfoPackage:
|
||||
|
||||
|
||||
state = AppState()
|
||||
|
||||
|
||||
def find_packages(query: str, qtype: str) -> list[Package | Source]:
|
||||
if qtype not in ["pkg", "binpkg"]:
|
||||
qtype = "pkg"
|
||||
parts = query.split()
|
||||
parts_lower = [p.lower() for p in parts]
|
||||
res_pkg: list[tuple[float, Package | Source]] = []
|
||||
|
||||
def get_score(name: str, parts: list[str]) -> float:
|
||||
score = 0.0
|
||||
for part in parts:
|
||||
if part not in name:
|
||||
return -1
|
||||
score += name.count(part) * len(part) / len(name)
|
||||
return score
|
||||
|
||||
if not query:
|
||||
pass
|
||||
elif qtype == "pkg":
|
||||
for s in state.sources.values():
|
||||
score = get_score(s.realname.lower(), parts_lower)
|
||||
if score >= 0:
|
||||
res_pkg.append((score, s))
|
||||
continue
|
||||
score = get_score(s.name.lower(), parts_lower)
|
||||
if score >= 0:
|
||||
res_pkg.append((score, s))
|
||||
res_pkg.sort(key=lambda e: (-e[0], e[1].name.lower()))
|
||||
elif qtype == "binpkg":
|
||||
for s in state.sources.values():
|
||||
for sub in s.packages.values():
|
||||
score = get_score(sub.realname.lower(), parts_lower)
|
||||
if score >= 0:
|
||||
res_pkg.append((score, sub))
|
||||
continue
|
||||
score = get_score(sub.name.lower(), parts_lower)
|
||||
if score >= 0:
|
||||
res_pkg.append((score, sub))
|
||||
res_pkg.sort(key=lambda e: (-e[0], e[1].name.lower()))
|
||||
|
||||
return [r[1] for r in res_pkg]
|
||||
|
||||
61
app/mcp.py
61
app/mcp.py
@ -1,8 +1,10 @@
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
from .utils import vercmp
|
||||
from pydantic import BaseModel, Field
|
||||
from .appstate import get_repositories, find_packages, Source
|
||||
|
||||
mcpapp = FastMCP(name="MathServer", stateless_http=True, json_response=False)
|
||||
mcpapp = FastMCP(name="MSYS2Server", stateless_http=True, json_response=False)
|
||||
|
||||
|
||||
@mcpapp.tool()
|
||||
@ -16,3 +18,60 @@ def msys2_vercmp(versionA: str, versionB: str) -> int:
|
||||
"""
|
||||
|
||||
return vercmp(versionA, versionB)
|
||||
|
||||
|
||||
class MCPRepository(BaseModel):
|
||||
"""A MSYS2 repository"""
|
||||
|
||||
name: str = Field(..., description="Name of the repository")
|
||||
pacman_url: str = Field(..., description="A full URL to a location where the database, packages, and signatures for this repository can be found.")
|
||||
src_url: str = Field(..., description="Git source URL of the repository, where the PKGBUILD and other source files can be found.")
|
||||
|
||||
|
||||
@mcpapp.tool()
|
||||
def msys2_list_repositories() -> list[MCPRepository]:
|
||||
"""Returns a list of MSYS2 repositories"""
|
||||
|
||||
res = []
|
||||
for repo in get_repositories():
|
||||
res.append(MCPRepository(name=repo.name, pacman_url=repo.url, src_url=repo.src_url))
|
||||
return res
|
||||
|
||||
|
||||
class MCPPackage(BaseModel):
|
||||
"""A MSYS2 package"""
|
||||
|
||||
name: str = Field(..., description="Name of the package")
|
||||
version: str = Field(..., description="Version of the package")
|
||||
description: str = Field(..., description="Description of the package")
|
||||
repository: str = Field(..., description="Repository where the package is located")
|
||||
|
||||
|
||||
class MCPBasePackage(BaseModel):
|
||||
"""A base package in MSYS2"""
|
||||
|
||||
name: str = Field(..., description="Name of the base package")
|
||||
description: str = Field(..., description="Description of the base package")
|
||||
packages: list[MCPPackage] = Field(default_factory=list, description="List of packages that belong to this base package")
|
||||
|
||||
|
||||
@mcpapp.tool()
|
||||
def msys2_search_base_packages(query: str, limit: int = 25) -> list[MCPBasePackage]:
|
||||
"""Find MSYS2 base packages
|
||||
|
||||
Args:
|
||||
query: The search query to find base packages.
|
||||
limit: The maximum number of results to return (default is 25).
|
||||
|
||||
Returns:
|
||||
A list of package names that match the query, sorted by relevance.
|
||||
"""
|
||||
|
||||
res = []
|
||||
for src in find_packages(query, "pkg")[:limit]:
|
||||
assert isinstance(src, Source)
|
||||
pkgres = []
|
||||
for pkg in src.packages.values():
|
||||
pkgres.append(MCPPackage(name=pkg.name, version=pkg.version, description=pkg.desc, repository=pkg.repo))
|
||||
res.append(MCPBasePackage(name=src.name, description=src.desc, packages=pkgres))
|
||||
return res
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for score, s in results %}
|
||||
{% for s in results %}
|
||||
<tr>
|
||||
<td><a href="{{ url_for('base', base_name=s.name) }}">{{ s.name }}</a></td>
|
||||
<td>{{ s.version }}</td>
|
||||
@ -95,7 +95,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for score, p in results %}
|
||||
{% for p in results %}
|
||||
<tr>
|
||||
<td><a href="{{ package_url(p) }}">{{ p.name }}</td>
|
||||
<td>{{ p.version }}</td>
|
||||
|
||||
40
app/web.py
40
app/web.py
@ -21,7 +21,7 @@ from fastapi_etag import Etag
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi_etag import add_exception_handler as add_etag_exception_handler
|
||||
|
||||
from .appstate import state, get_repositories, Package, Source, DepType, SrcInfoPackage, get_base_group_name, Vulnerability, Severity, PackageKey
|
||||
from .appstate import state, get_repositories, Package, Source, DepType, SrcInfoPackage, get_base_group_name, Vulnerability, Severity, PackageKey, find_packages
|
||||
from .utils import extract_upstream_version, version_is_newer_than
|
||||
|
||||
router = APIRouter(default_response_class=HTMLResponse)
|
||||
@ -730,44 +730,10 @@ async def search(request: Request, response: Response, q: str = "", t: str = "")
|
||||
if qtype not in ["pkg", "binpkg"]:
|
||||
qtype = "pkg"
|
||||
|
||||
parts = query.split()
|
||||
parts_lower = [p.lower() for p in parts]
|
||||
res_pkg: list[tuple[float, Package | Source]] = []
|
||||
|
||||
def get_score(name: str, parts: list[str]) -> float:
|
||||
score = 0.0
|
||||
for part in parts:
|
||||
if part not in name:
|
||||
return -1
|
||||
score += name.count(part) * len(part) / len(name)
|
||||
return score
|
||||
|
||||
if not query:
|
||||
pass
|
||||
elif qtype == "pkg":
|
||||
for s in state.sources.values():
|
||||
score = get_score(s.realname.lower(), parts_lower)
|
||||
if score >= 0:
|
||||
res_pkg.append((score, s))
|
||||
continue
|
||||
score = get_score(s.name.lower(), parts_lower)
|
||||
if score >= 0:
|
||||
res_pkg.append((score, s))
|
||||
res_pkg.sort(key=lambda e: (-e[0], e[1].name.lower()))
|
||||
elif qtype == "binpkg":
|
||||
for s in state.sources.values():
|
||||
for sub in s.packages.values():
|
||||
score = get_score(sub.realname.lower(), parts_lower)
|
||||
if score >= 0:
|
||||
res_pkg.append((score, sub))
|
||||
continue
|
||||
score = get_score(sub.name.lower(), parts_lower)
|
||||
if score >= 0:
|
||||
res_pkg.append((score, sub))
|
||||
res_pkg.sort(key=lambda e: (-e[0], e[1].name.lower()))
|
||||
results = find_packages(query, qtype)
|
||||
|
||||
return templates.TemplateResponse(request, "search.html", {
|
||||
"results": res_pkg,
|
||||
"results": results,
|
||||
"query": query,
|
||||
"qtype": qtype,
|
||||
}, headers=dict(response.headers))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user