From 1d5fabae70ce5000d2edcb8afe209ce6acd665b9 Mon Sep 17 00:00:00 2001 From: John Pazzelli Date: Thu, 26 Mar 2026 13:17:54 -0400 Subject: [PATCH 1/5] Refactored startup logic to allow FastMCP object to be exposed --- src/server.py | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/server.py b/src/server.py index eac59ef..bb5a95f 100644 --- a/src/server.py +++ b/src/server.py @@ -1007,6 +1007,29 @@ async def run_async_server(self, transport="stdio", host="127.0.0.1", port=9001, finally: await self.close_pool() + # Sync server start logic + def start(self): + exit_code = 0 + + try: + # 2. Use anyio.run to manage the event loop and call the main async server logic + anyio.run( + partial(self.run_async_server, + transport=args.transport, + host=args.host, + port=args.port, + path=args.path) + ) + logger.info("Server finished gracefully.") + + except KeyboardInterrupt: + logger.info("Server execution interrupted by user.") + except Exception as e: + logger.critical(f"Server failed to start or crashed: {e}", exc_info=True) + exit_code = 1 + finally: + logger.info(f"Server exiting with code {exit_code}.") + # --- Main Execution Block --- if __name__ == "__main__": @@ -1023,23 +1046,4 @@ async def run_async_server(self, transport="stdio", host="127.0.0.1", port=9001, # 1. Create the server instance server = MariaDBServer() - exit_code = 0 - - try: - # 2. Use anyio.run to manage the event loop and call the main async server logic - anyio.run( - partial(server.run_async_server, - transport=args.transport, - host=args.host, - port=args.port, - path=args.path) - ) - logger.info("Server finished gracefully.") - - except KeyboardInterrupt: - logger.info("Server execution interrupted by user.") - except Exception as e: - logger.critical(f"Server failed to start or crashed: {e}", exc_info=True) - exit_code = 1 - finally: - logger.info(f"Server exiting with code {exit_code}.") \ No newline at end of file + server.start() \ No newline at end of file From 5e64e2d15baa85672c22dbb9e0e328321a9daa8c Mon Sep 17 00:00:00 2001 From: John Pazzelli Date: Thu, 26 Mar 2026 20:27:23 -0400 Subject: [PATCH 2/5] Refactored startup logic to allow FastMCP object to be exposed --- src/server.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/server.py b/src/server.py index bb5a95f..d9fe733 100644 --- a/src/server.py +++ b/src/server.py @@ -1008,17 +1008,17 @@ async def run_async_server(self, transport="stdio", host="127.0.0.1", port=9001, await self.close_pool() # Sync server start logic - def start(self): + def start(self, transport="stdio", host="127.0.0.1", port=9001, path="/mcp"): exit_code = 0 try: # 2. Use anyio.run to manage the event loop and call the main async server logic anyio.run( partial(self.run_async_server, - transport=args.transport, - host=args.host, - port=args.port, - path=args.path) + transport=transport, + host=host, + port=port, + path=path) ) logger.info("Server finished gracefully.") @@ -1046,4 +1046,4 @@ def start(self): # 1. Create the server instance server = MariaDBServer() - server.start() \ No newline at end of file + server.start(args.transport, args.host, args.port, args.path) \ No newline at end of file From 25c4fb706721aef26619bc29757a16487bbd3c5a Mon Sep 17 00:00:00 2001 From: John Pazzelli Date: Thu, 26 Mar 2026 20:58:34 -0400 Subject: [PATCH 3/5] Refactored startup logic to allow FastMCP object to be exposed --- src/server.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/server.py b/src/server.py index d9fe733..fc743bb 100644 --- a/src/server.py +++ b/src/server.py @@ -1031,8 +1031,7 @@ def start(self, transport="stdio", host="127.0.0.1", port=9001, path="/mcp"): logger.info(f"Server exiting with code {exit_code}.") -# --- Main Execution Block --- -if __name__ == "__main__": +def get_arg_parser(): parser = argparse.ArgumentParser(description="MariaDB MCP Server") parser.add_argument('--transport', type=str, default='stdio', choices=['stdio', 'sse', 'http'], help='MCP transport protocol (stdio, sse, or http)') @@ -1042,7 +1041,12 @@ def start(self, transport="stdio", host="127.0.0.1", port=9001, path="/mcp"): help='Port for SSE or HTTP transport') parser.add_argument('--path', type=str, default='/mcp', help='Path for HTTP transport (default: /mcp)') - args = parser.parse_args() + return parser.parse_args() + + +# --- Main Execution Block --- +if __name__ == "__main__": + args = get_arg_parser() # 1. Create the server instance server = MariaDBServer() From b041bffb47f051173823d545684a6587885ba117 Mon Sep 17 00:00:00 2001 From: John Pazzelli Date: Thu, 26 Mar 2026 21:00:00 -0400 Subject: [PATCH 4/5] Refactored startup logic to allow FastMCP object to be exposed --- src/server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server.py b/src/server.py index fc743bb..7d98d6a 100644 --- a/src/server.py +++ b/src/server.py @@ -1031,7 +1031,7 @@ def start(self, transport="stdio", host="127.0.0.1", port=9001, path="/mcp"): logger.info(f"Server exiting with code {exit_code}.") -def get_arg_parser(): +def get_arg_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="MariaDB MCP Server") parser.add_argument('--transport', type=str, default='stdio', choices=['stdio', 'sse', 'http'], help='MCP transport protocol (stdio, sse, or http)') @@ -1041,12 +1041,12 @@ def get_arg_parser(): help='Port for SSE or HTTP transport') parser.add_argument('--path', type=str, default='/mcp', help='Path for HTTP transport (default: /mcp)') - return parser.parse_args() + return parser # --- Main Execution Block --- if __name__ == "__main__": - args = get_arg_parser() + args = get_arg_parser().parse_args() # 1. Create the server instance server = MariaDBServer() From 651e16d4ca1e28a04d7ecd4f36dba065d8dae293 Mon Sep 17 00:00:00 2001 From: John Pazzelli Date: Sat, 28 Mar 2026 14:44:28 -0400 Subject: [PATCH 5/5] Refactored startup logic to allow FastMCP object to be exposed --- src/tests/test_custom_resource.py | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/tests/test_custom_resource.py diff --git a/src/tests/test_custom_resource.py b/src/tests/test_custom_resource.py new file mode 100644 index 0000000..2b5a4b5 --- /dev/null +++ b/src/tests/test_custom_resource.py @@ -0,0 +1,51 @@ +""" +Tests that the MariaDBServer exposes its FastMCP instance so callers can +register additional resources before starting the server. + +This mirrors the pattern used by mcp_fmd_server.py, which adds a +``schema://context`` resource to the ``mcp`` object after construction. +""" + +import sys +import unittest +from pathlib import Path + +import anyio +from fastmcp.client import Client + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from server import MariaDBServer + +_SCHEMA_CONTENT = "# Test Schema\nThis is test schema documentation." + + +class TestCustomResource(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self): + self.server = MariaDBServer() + + # Register a custom resource on the exposed FastMCP instance — + # this is the pattern the FMD wrapper (mcp_fmd_server.py) uses. + @self.server.mcp.resource("schema://context") + def schema_context() -> str: + """FMD AI Schema documentation.""" + return _SCHEMA_CONTENT + + async def test_custom_resource_is_listed(self): + """The schema://context resource should appear in the resource list.""" + async with Client(self.server.mcp) as client: + resources = await client.list_resources() + uris = [str(r.uri) for r in resources] + self.assertIn("schema://context", uris) + + async def test_custom_resource_content(self): + """Reading schema://context should return the registered content.""" + async with Client(self.server.mcp) as client: + result = await client.read_resource("schema://context") + # result is a list of resource content objects; grab the first text + text = result[0].text if result else "" + self.assertEqual(text, _SCHEMA_CONTENT) + + +if __name__ == "__main__": + unittest.main()