mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-06 23:21:16 +08:00
Compare commits
2 Commits
codex/add-
...
codex/add-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
353041f929 | ||
|
|
26ca9fc916 |
@@ -260,3 +260,14 @@ class RecentPostsResponse(BaseModel):
|
||||
|
||||
|
||||
CommentData.model_rebuild()
|
||||
|
||||
|
||||
class PostDetail(PostSummary):
|
||||
"""Detailed information for a single post, including comments."""
|
||||
|
||||
comments: list[CommentData] = Field(
|
||||
default_factory=list,
|
||||
description="Comments that belong to the post.",
|
||||
)
|
||||
|
||||
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
||||
|
||||
@@ -84,6 +84,18 @@ class SearchClient:
|
||||
)
|
||||
return [self._ensure_dict(entry) for entry in payload]
|
||||
|
||||
async def get_post(self, post_id: int, token: str | None = None) -> dict[str, Any]:
|
||||
"""Retrieve the detailed payload for a single post."""
|
||||
|
||||
client = self._get_client()
|
||||
headers = {"Accept": "application/json"}
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
response = await client.get(f"/api/posts/{post_id}", headers=headers)
|
||||
response.raise_for_status()
|
||||
return self._ensure_dict(response.json())
|
||||
|
||||
async def aclose(self) -> None:
|
||||
"""Dispose of the underlying HTTP client."""
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from .config import get_settings
|
||||
from .schemas import (
|
||||
CommentData,
|
||||
CommentReplyResult,
|
||||
PostDetail,
|
||||
PostSummary,
|
||||
RecentPostsResponse,
|
||||
SearchResponse,
|
||||
@@ -41,7 +42,8 @@ app = FastMCP(
|
||||
name="openisle-mcp",
|
||||
instructions=(
|
||||
"Use this server to search OpenIsle content, reply to comments with an authentication "
|
||||
"token, and list posts created within a recent time window."
|
||||
"token, retrieve details for a specific post, and list posts created within a recent time "
|
||||
"window."
|
||||
),
|
||||
host=settings.host,
|
||||
port=settings.port,
|
||||
@@ -229,6 +231,69 @@ async def recent_posts(
|
||||
return RecentPostsResponse(minutes=minutes, total=len(posts), posts=posts)
|
||||
|
||||
|
||||
@app.tool(
|
||||
name="get_post",
|
||||
description="Retrieve detailed information for a single post.",
|
||||
structured_output=True,
|
||||
)
|
||||
async def get_post(
|
||||
post_id: Annotated[
|
||||
int,
|
||||
PydanticField(ge=1, description="Identifier of the post to retrieve."),
|
||||
],
|
||||
token: Annotated[
|
||||
str | None,
|
||||
PydanticField(
|
||||
default=None,
|
||||
description="Optional JWT bearer token to view the post as an authenticated user.",
|
||||
),
|
||||
] = None,
|
||||
ctx: Context | None = None,
|
||||
) -> PostDetail:
|
||||
"""Fetch post details from the backend and validate the response."""
|
||||
|
||||
sanitized_token = token.strip() if isinstance(token, str) else None
|
||||
if sanitized_token == "":
|
||||
sanitized_token = None
|
||||
|
||||
try:
|
||||
raw_post = await search_client.get_post(post_id, sanitized_token)
|
||||
except httpx.HTTPStatusError as exc: # pragma: no cover - network errors
|
||||
status_code = exc.response.status_code
|
||||
if status_code == 404:
|
||||
message = f"Post {post_id} was not found."
|
||||
elif status_code == 401:
|
||||
message = "Authentication failed while retrieving the post."
|
||||
elif status_code == 403:
|
||||
message = "The provided token is not authorized to view this post."
|
||||
else:
|
||||
message = (
|
||||
"OpenIsle backend returned HTTP "
|
||||
f"{status_code} while retrieving post {post_id}."
|
||||
)
|
||||
if ctx is not None:
|
||||
await ctx.error(message)
|
||||
raise ValueError(message) from exc
|
||||
except httpx.RequestError as exc: # pragma: no cover - network errors
|
||||
message = f"Unable to reach OpenIsle backend post service: {exc}."
|
||||
if ctx is not None:
|
||||
await ctx.error(message)
|
||||
raise ValueError(message) from exc
|
||||
|
||||
try:
|
||||
post = PostDetail.model_validate(raw_post)
|
||||
except ValidationError as exc:
|
||||
message = "Received malformed data from the post detail endpoint."
|
||||
if ctx is not None:
|
||||
await ctx.error(message)
|
||||
raise ValueError(message) from exc
|
||||
|
||||
if ctx is not None:
|
||||
await ctx.info(f"Retrieved post {post_id} successfully.")
|
||||
|
||||
return post
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the MCP server using the configured transport."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user