Skip to content

Commit e64a95f

Browse files
kyoncalKyon Caldera
andauthored
Fix dynamic tooling (#28)
* fix(server.py): remove redundant proxy and bypass mc_proxy_manager * feat(tools_filter.py): add tool_filter.py as filtering middleware * feat(server.py): add middleware directly to proxy Instead of using the mcp_proxy_manager, here we directly add middleware to the proxy. This effectively makes the mcp_proxy_manager redundant. * style(server.py,-mcp_proxy_manager.py): cleanup remnants of mcp_proxy_manager * fix(unit_tests): fix unit tests that were broken as a result of removing mcp_proxy_manager.py * fix(server.py): remove rate limiting middleware * fix(tool_filter.py): add type hint to call_next * refactor(tool_filter.py): use class name for logger config * perf(tool_filter.py): return early in tool filtering if read_only is false * fix(logging_config.py): change log output to stderr this fixes issues with logging, especially with printing to the terminal. * style: fix failing stylecheck * feat(server.py): backport retries as an argument to work without an mcp_proxy_manager --------- Co-authored-by: Kyon Caldera <kyonc@amazon.com>
1 parent 4d8148d commit e64a95f

7 files changed

Lines changed: 365 additions & 753 deletions

File tree

aws_mcp_proxy/logging_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def configure_logging(level: Optional[str] = None) -> None:
3434
date_format = '%Y-%m-%d %H:%M:%S'
3535

3636
# Create console handler with formatting
37-
console_handler = logging.StreamHandler(sys.stdout)
37+
console_handler = logging.StreamHandler(sys.stderr)
3838
console_handler.setFormatter(logging.Formatter(log_format, date_format))
3939

4040
# Configure root logger

aws_mcp_proxy/mcp_proxy_manager.py

Lines changed: 0 additions & 132 deletions
This file was deleted.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
from collections.abc import Awaitable, Callable
17+
from fastmcp.server.middleware import Middleware, MiddlewareContext
18+
from fastmcp.tools.tool import Tool
19+
20+
21+
class ToolFilteringMiddleware(Middleware):
22+
"""Middleware to filter tools based on read only flag."""
23+
24+
def __init__(self, read_only: bool, logger: logging.Logger | None = None):
25+
"""Initialize the middleware."""
26+
self.read_only = read_only
27+
self.logger = logger or logging.getLogger(__name__)
28+
29+
async def on_list_tools(
30+
self,
31+
context: MiddlewareContext,
32+
call_next: Callable[[MiddlewareContext], Awaitable[list[Tool]]],
33+
):
34+
"""Filter tools based on read only flag."""
35+
# Get list of FastMCP Components
36+
tools = await call_next(context)
37+
self.logger.info(f'Filtering tools for read only: {self.read_only}')
38+
39+
# If not read only, return the list of tools as is
40+
if not self.read_only:
41+
return tools
42+
43+
filtered_tools = []
44+
for tool in tools:
45+
# Check the tool annotations and disable if needed
46+
annotations = tool.annotations
47+
48+
# Skip the tools with no readOnlyHint=True annotation
49+
read_only_hint = getattr(annotations, 'readOnlyHint', False)
50+
if not read_only_hint:
51+
# Skip tools that don't have readOnlyHint=True
52+
self.logger.info(f'Skipping tool {tool.name} needing write permissions')
53+
continue
54+
55+
filtered_tools.append(tool)
56+
57+
return filtered_tools

aws_mcp_proxy/server.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@
2727
import logging
2828
import os
2929
from aws_mcp_proxy.logging_config import configure_logging
30-
from aws_mcp_proxy.mcp_proxy_manager import McpProxyManager
30+
from aws_mcp_proxy.middleware.tool_filter import ToolFilteringMiddleware
3131
from aws_mcp_proxy.utils import (
3232
create_transport_with_sigv4,
3333
determine_aws_region,
3434
determine_service_name,
3535
)
36+
from fastmcp.server.middleware.error_handling import RetryMiddleware
3637
from fastmcp.server.server import FastMCP
3738
from typing import Any
3839

@@ -62,10 +63,38 @@ async def setup_mcp_mode(local_mcp: FastMCP, args) -> None:
6263

6364
# Create proxy with the transport
6465
proxy = FastMCP.as_proxy(transport)
66+
add_tool_filtering_middleware(proxy, args.read_only)
6567

66-
# Use McpProxyManager to add proxy content
67-
proxy_manager = McpProxyManager(local_mcp, args.read_only)
68-
await proxy_manager.add_proxy_content(proxy, args.retries)
68+
if args.retries:
69+
add_retry_middleware(proxy, args.retries)
70+
71+
await proxy.run_async()
72+
73+
74+
def add_tool_filtering_middleware(mcp: FastMCP, read_only: bool = False) -> None:
75+
"""Add tool filtering middleware to target MCP server.
76+
77+
Args:
78+
mcp: The FastMCP instance to add tool filtering to
79+
read_only: Whether or not to filter out tools that require write permissions
80+
"""
81+
logger.info('Adding tool filtering middleware')
82+
mcp.add_middleware(
83+
ToolFilteringMiddleware(
84+
read_only=read_only,
85+
)
86+
)
87+
88+
89+
def add_retry_middleware(mcp: FastMCP, retries: int) -> None:
90+
"""Add retry with exponential backoff middleware to target MCP server.
91+
92+
Args:
93+
mcp: The FastMCP instance to add exponential backoff to
94+
retries: number of retries with which to configure the retry middleware
95+
"""
96+
logger.info('Adding retry middleware')
97+
mcp.add_middleware(RetryMiddleware(retries))
6998

7099

71100
def parse_args():
@@ -155,7 +184,6 @@ async def setup_and_run():
155184
await setup_mcp_mode(mcp, args)
156185

157186
logger.info('Server setup complete, starting MCP server')
158-
await mcp.run_async()
159187

160188
except Exception as e:
161189
logger.error('Failed to start server: %s', e)

0 commit comments

Comments
 (0)