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()
|
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 mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
from .utils import vercmp
|
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()
|
@mcpapp.tool()
|
||||||
@ -16,3 +18,60 @@ def msys2_vercmp(versionA: str, versionB: str) -> int:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
return vercmp(versionA, versionB)
|
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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for score, s in results %}
|
{% for s in results %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ url_for('base', base_name=s.name) }}">{{ s.name }}</a></td>
|
<td><a href="{{ url_for('base', base_name=s.name) }}">{{ s.name }}</a></td>
|
||||||
<td>{{ s.version }}</td>
|
<td>{{ s.version }}</td>
|
||||||
@ -95,7 +95,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for score, p in results %}
|
{% for p in results %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ package_url(p) }}">{{ p.name }}</td>
|
<td><a href="{{ package_url(p) }}">{{ p.name }}</td>
|
||||||
<td>{{ p.version }}</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.staticfiles import StaticFiles
|
||||||
from fastapi_etag import add_exception_handler as add_etag_exception_handler
|
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
|
from .utils import extract_upstream_version, version_is_newer_than
|
||||||
|
|
||||||
router = APIRouter(default_response_class=HTMLResponse)
|
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"]:
|
if qtype not in ["pkg", "binpkg"]:
|
||||||
qtype = "pkg"
|
qtype = "pkg"
|
||||||
|
|
||||||
parts = query.split()
|
results = find_packages(query, qtype)
|
||||||
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 templates.TemplateResponse(request, "search.html", {
|
return templates.TemplateResponse(request, "search.html", {
|
||||||
"results": res_pkg,
|
"results": results,
|
||||||
"query": query,
|
"query": query,
|
||||||
"qtype": qtype,
|
"qtype": qtype,
|
||||||
}, headers=dict(response.headers))
|
}, headers=dict(response.headers))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user