Created
September 18, 2025 19:21
-
-
Save kordless/ad5a8625762bd75060dfad1ad95a7e9c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sticky howto-dynamic-simple-tool-proxy | |
Created: 2025-09-18T19:13:19.306879Z | |
Last modified: 2025-09-18T19:13:19.306879Z | |
Last accessed: 2025-09-18T19:20:57.994597Z | |
URL: app://sticky/howto-dynamic-simple-tool-proxy | |
Dynamic MCP Proxy (Hot‑Reload) — How‑To | |
Overview | |
- Expose a tool via an MCP server that dynamically imports the underlying implementation from a target file. | |
- Detect file changes via mtime and reload on demand; no server restarts required. | |
Files | |
- MCP/dynamic_simple_tool_proxy_mcp.py — dynamic proxy server (already added). | |
- MCP/simple-tool.py — underlying tool implementation; must define `simple_tool` (async or sync). | |
Pseudocode (core idea) | |
class DynamicLoader: | |
def __init__(self, path): | |
self.path = os.path.abspath(path) | |
self.loaded = None # LoadedModule(module, mtime) | |
def stat_mtime(self): | |
try: return os.path.getmtime(self.path) | |
except OSError: return None | |
def _load_fresh(self): | |
spec = importlib.util.spec_from_file_location(unique_name(), self.path) | |
mod = importlib.util.module_from_spec(spec) | |
spec.loader.exec_module(mod) | |
return LoadedModule(mod, self.stat_mtime()) | |
def clear(self): | |
self.loaded = None | |
def get_module(self, force_reload=False): | |
m = self.stat_mtime() | |
if m is None: raise FileNotFoundError(self.path) | |
if force_reload or self.loaded is None or self.loaded.mtime < m: | |
self.loaded = self._load_fresh() | |
return self.loaded | |
# MCP tools | |
@mcp.tool() | |
async def simple_tool(): | |
loaded = loader.get_module() | |
fn = getattr(loaded.module, "simple_tool") # async or sync | |
return await fn() if asyncio.iscoroutinefunction(fn) else str(fn()) | |
@mcp.tool() | |
async def reload_wrapped(): | |
loader.clear(); loader.get_module(force_reload=True) | |
return "reloaded" | |
@mcp.tool() | |
async def proxy_status(): | |
return f"target={loader.target_path} mtime={loader.stat_mtime()} cached={(loader._loaded.mtime if loader._loaded else None)}" | |
Config (Codex MCP) | |
[mcp_servers.simple_tool_proxy] | |
command = "python3" | |
args = ["MCP/dynamic_simple_tool_proxy_mcp.py", "--target", "MCP/simple-tool.py"] | |
working_directory = "/mnt/c/Users/kord/Code/Gnosis" | |
Usage | |
- Connect your MCP client to `simple_tool_proxy`. | |
- Call `proxy_status` to verify target + mtime. | |
- Call `simple_tool` to execute the underlying tool. | |
- Edit MCP/simple-tool.py and save; call `simple_tool` again to see changes. | |
- Use `reload_wrapped` to force a clear+reload if needed. | |
Requirements & Notes | |
- Python 3.10+; `mcp.server.fastmcp` installed for the proxy server. | |
- Underlying module should not start its own server at import-time (the included file is safe). | |
- Exceptions inside the underlying tool propagate through the proxy. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment