Compare commits

...

2 Commits

Author SHA1 Message Date
Tim
e585100625 Remove token parameters from MCP tools 2025-10-28 01:32:02 +08:00
Tim
e94471b53e Merge pull request #1098 from nagisa77/codex/store-accesstoken-as-jwt-token
Cache MCP session JWT tokens
2025-10-28 01:20:52 +08:00

View File

@@ -57,7 +57,9 @@ class SessionTokenManager:
def __init__(self) -> None: def __init__(self) -> None:
self._tokens: WeakKeyDictionary[Any, str] = WeakKeyDictionary() self._tokens: WeakKeyDictionary[Any, str] = WeakKeyDictionary()
def resolve(self, ctx: Context | None, token: str | None) -> str | None: def resolve(
self, ctx: Context | None, token: str | None = None
) -> str | None:
"""Resolve and optionally persist the token for the current session.""" """Resolve and optionally persist the token for the current session."""
session = self._get_session(ctx) session = self._get_session(ctx)
@@ -127,8 +129,8 @@ async def lifespan(_: FastMCP):
app = FastMCP( app = FastMCP(
name="openisle-mcp", name="openisle-mcp",
instructions=( instructions=(
"Use this server to search OpenIsle content, reply to posts and comments with an " "Use this server to search OpenIsle content, reply to posts and comments with "
"authentication token, retrieve details for a specific post, list posts created " "session-managed authentication, retrieve details for a specific post, list posts created "
"within a recent time window, and review unread notification messages." "within a recent time window, and review unread notification messages."
), ),
host=settings.host, host=settings.host,
@@ -190,7 +192,7 @@ async def search(
@app.tool( @app.tool(
name="reply_to_post", name="reply_to_post",
description="Create a comment on a post using an authentication token.", description="Create a comment on a post using session authentication.",
structured_output=True, structured_output=True,
) )
async def reply_to_post( async def reply_to_post(
@@ -209,15 +211,6 @@ async def reply_to_post(
description="Optional captcha solution if the backend requires it.", description="Optional captcha solution if the backend requires it.",
), ),
] = None, ] = None,
token: Annotated[
str | None,
PydanticField(
default=None,
description=(
"Optional JWT bearer token. When omitted the configured access token is used."
),
),
] = None,
ctx: Context | None = None, ctx: Context | None = None,
) -> CommentCreateResult: ) -> CommentCreateResult:
"""Create a comment on a post and return the backend payload.""" """Create a comment on a post and return the backend payload."""
@@ -228,7 +221,7 @@ async def reply_to_post(
sanitized_captcha = captcha.strip() if isinstance(captcha, str) else None sanitized_captcha = captcha.strip() if isinstance(captcha, str) else None
resolved_token = session_token_manager.resolve(ctx, token) resolved_token = session_token_manager.resolve(ctx)
try: try:
logger.info( logger.info(
@@ -297,7 +290,7 @@ async def reply_to_post(
@app.tool( @app.tool(
name="reply_to_comment", name="reply_to_comment",
description="Reply to an existing comment using an authentication token.", description="Reply to an existing comment using session authentication.",
structured_output=True, structured_output=True,
) )
async def reply_to_comment( async def reply_to_comment(
@@ -316,15 +309,6 @@ async def reply_to_comment(
description="Optional captcha solution if the backend requires it.", description="Optional captcha solution if the backend requires it.",
), ),
] = None, ] = None,
token: Annotated[
str | None,
PydanticField(
default=None,
description=(
"Optional JWT bearer token. When omitted the configured access token is used."
),
),
] = None,
ctx: Context | None = None, ctx: Context | None = None,
) -> CommentReplyResult: ) -> CommentReplyResult:
"""Create a reply for a comment and return the backend payload.""" """Create a reply for a comment and return the backend payload."""
@@ -335,7 +319,7 @@ async def reply_to_comment(
sanitized_captcha = captcha.strip() if isinstance(captcha, str) else None sanitized_captcha = captcha.strip() if isinstance(captcha, str) else None
resolved_token = session_token_manager.resolve(ctx, token) resolved_token = session_token_manager.resolve(ctx)
try: try:
logger.info( logger.info(
@@ -462,18 +446,11 @@ async def get_post(
int, int,
PydanticField(ge=1, description="Identifier of the post to retrieve."), 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, ctx: Context | None = None,
) -> PostDetail: ) -> PostDetail:
"""Fetch post details from the backend and validate the response.""" """Fetch post details from the backend and validate the response."""
resolved_token = session_token_manager.resolve(ctx, token) resolved_token = session_token_manager.resolve(ctx)
try: try:
logger.info("Fetching post details for post_id=%s", post_id) logger.info("Fetching post details for post_id=%s", post_id)
@@ -542,20 +519,11 @@ async def list_unread_messages(
description="Number of unread notifications to include per page.", description="Number of unread notifications to include per page.",
), ),
] = 30, ] = 30,
token: Annotated[
str | None,
PydanticField(
default=None,
description=(
"Optional JWT bearer token. When omitted the configured access token is used."
),
),
] = None,
ctx: Context | None = None, ctx: Context | None = None,
) -> UnreadNotificationsResponse: ) -> UnreadNotificationsResponse:
"""Retrieve unread notifications and return structured data.""" """Retrieve unread notifications and return structured data."""
resolved_token = session_token_manager.resolve(ctx, token) resolved_token = session_token_manager.resolve(ctx)
try: try:
logger.info( logger.info(