import re
from typing import Any, Literal
from fastmcp import FastMCP
from fastmcp.tools import Tool as FastMCPTool
from loguru import logger
from mcp.types import Tool as MCPTool
from starlette.applications import Starlette
from starlette.middleware import Middleware as ASGIMiddleware
from mcp_jenkins.core import AuthMiddleware, LifespanContext, lifespan
__all__ = ['mcp']
class JenkinsMCP(FastMCP[LifespanContext]):
async def _list_tools_mcp(self) -> list[MCPTool]:
"""List available tools, filtering based on lifespan context (e.g. read-only mode)
Returns:
List of available mcp tools
"""
request_context = self._mcp_server.request_context
if request_context is None or request_context.lifespan_context is None:
logger.warning('Lifespan context not available during _list_tools_mcp call.')
return []
jenkins_lifespan_context: LifespanContext = request_context.lifespan_context
all_tools: dict[str, FastMCPTool] = await self.get_tools()
mcp_tools: list[MCPTool] = []
for registered_name, tool in all_tools.items():
if not tool:
continue
if jenkins_lifespan_context.read_only and 'read' not in tool.tags:
logger.debug(f'Excluding tool [{registered_name}] due to read-only mode')
continue
if jenkins_lifespan_context.tool_regex and not re.search(
jenkins_lifespan_context.tool_regex, registered_name
):
logger.debug(f'Excluding tool [{registered_name}] due to tool_regex filter')
continue
mcp_tools.append(tool.to_mcp_tool(name=registered_name))
return mcp_tools
def http_app(
self,
path: str | None = None,
middleware: list[ASGIMiddleware] | None = None,
transport: Literal['http', 'streamable-http', 'sse'] = 'http',
**kwargs: Any, # noqa: ANN401
) -> 'Starlette':
"""Override to add JenkinsAuthMiddleware"""
jenkins_auth_mw = ASGIMiddleware(AuthMiddleware)
final_middleware_list = [jenkins_auth_mw]
if middleware:
final_middleware_list.extend(middleware)
return super().http_app(path=path, middleware=final_middleware_list, transport=transport, **kwargs)
mcp = JenkinsMCP('mcp-jenkins', lifespan=lifespan)
# Import tool modules to register them with the MCP server
# This must happen after mcp is created so the @mcp.tool() decorators can reference it
from mcp_jenkins.server import build, item, node, queue # noqa: F401, E402