diff --git a/fuzzforge-common/src/fuzzforge_common/hub/client.py b/fuzzforge-common/src/fuzzforge_common/hub/client.py index 90a753b..cb12dcf 100644 --- a/fuzzforge-common/src/fuzzforge_common/hub/client.py +++ b/fuzzforge-common/src/fuzzforge_common/hub/client.py @@ -176,6 +176,7 @@ class HubClient: arguments: dict[str, Any], *, timeout: int | None = None, + extra_volumes: list[str] | None = None, ) -> dict[str, Any]: """Execute a tool on a hub server. @@ -183,6 +184,7 @@ class HubClient: :param tool_name: Name of the tool to execute. :param arguments: Tool arguments. :param timeout: Execution timeout (uses default if None). + :param extra_volumes: Additional Docker volume mounts to inject. :returns: Tool execution result. :raises HubClientError: If execution fails. @@ -199,7 +201,7 @@ class HubClient: ) try: - async with self._connect(config) as (reader, writer): + async with self._connect(config, extra_volumes=extra_volumes) as (reader, writer): # Initialise MCP session (skip for persistent — already done) if not self._persistent_sessions.get(config.name): await self._initialize_session(reader, writer, config.name) @@ -248,6 +250,7 @@ class HubClient: async def _connect( self, config: HubServerConfig, + extra_volumes: list[str] | None = None, ) -> AsyncGenerator[tuple[asyncio.StreamReader, asyncio.StreamWriter], None]: """Connect to an MCP server. @@ -256,6 +259,7 @@ class HubClient: ephemeral per-call connection logic. :param config: Server configuration. + :param extra_volumes: Additional Docker volume mounts to inject. :yields: Tuple of (reader, writer) for communication. """ @@ -268,7 +272,7 @@ class HubClient: # Ephemeral connection (original behaviour) if config.type == HubServerType.DOCKER: - async with self._connect_docker(config) as streams: + async with self._connect_docker(config, extra_volumes=extra_volumes) as streams: yield streams elif config.type == HubServerType.COMMAND: async with self._connect_command(config) as streams: @@ -284,10 +288,12 @@ class HubClient: async def _connect_docker( self, config: HubServerConfig, + extra_volumes: list[str] | None = None, ) -> AsyncGenerator[tuple[asyncio.StreamReader, asyncio.StreamWriter], None]: """Connect to a Docker-based MCP server. :param config: Server configuration with image name. + :param extra_volumes: Additional volume mounts to inject (e.g. project assets). :yields: Tuple of (reader, writer) for stdio communication. """ @@ -302,10 +308,14 @@ class HubClient: for cap in config.capabilities: cmd.extend(["--cap-add", cap]) - # Add volumes + # Add volumes from server config for volume in config.volumes: cmd.extend(["-v", os.path.expanduser(volume)]) + # Add extra volumes (e.g. project assets injected at runtime) + for volume in (extra_volumes or []): + cmd.extend(["-v", os.path.expanduser(volume)]) + # Add environment variables for key, value in config.environment.items(): cmd.extend(["-e", f"{key}={value}"]) diff --git a/fuzzforge-common/src/fuzzforge_common/hub/executor.py b/fuzzforge-common/src/fuzzforge_common/hub/executor.py index 7fef168..ab3b293 100644 --- a/fuzzforge-common/src/fuzzforge_common/hub/executor.py +++ b/fuzzforge-common/src/fuzzforge_common/hub/executor.py @@ -180,12 +180,14 @@ class HubExecutor: arguments: dict[str, Any] | None = None, *, timeout: int | None = None, + extra_volumes: list[str] | None = None, ) -> HubExecutionResult: """Execute a hub tool. :param identifier: Tool identifier (hub:server:tool or server:tool). :param arguments: Tool arguments. :param timeout: Execution timeout. + :param extra_volumes: Additional Docker volume mounts to inject. :returns: Execution result. """ @@ -232,6 +234,7 @@ class HubExecutor: tool_name_to_use or tool_name, arguments, timeout=timeout, + extra_volumes=extra_volumes, ) return HubExecutionResult( success=True, @@ -268,6 +271,7 @@ class HubExecutor: tool.name, arguments, timeout=timeout, + extra_volumes=extra_volumes, ) return HubExecutionResult( success=True, diff --git a/fuzzforge-mcp/src/fuzzforge_mcp/tools/hub.py b/fuzzforge-mcp/src/fuzzforge_mcp/tools/hub.py index eedb89e..b362f5f 100644 --- a/fuzzforge-mcp/src/fuzzforge_mcp/tools/hub.py +++ b/fuzzforge-mcp/src/fuzzforge_mcp/tools/hub.py @@ -16,7 +16,7 @@ from fastmcp import FastMCP from fastmcp.exceptions import ToolError from fuzzforge_common.hub import HubExecutor, HubServerConfig, HubServerType -from fuzzforge_mcp.dependencies import get_settings +from fuzzforge_mcp.dependencies import get_project_path, get_settings, get_storage mcp: FastMCP = FastMCP() @@ -180,10 +180,30 @@ async def execute_hub_tool( try: executor = _get_hub_executor() + # Inject project assets as Docker volume mounts if configured. + # Mounts the assets directory at the standard paths used by hub tools: + # /app/uploads — binwalk, and other tools that use UPLOAD_DIR + # /app/samples — yara, capa, and other tools that use SAMPLES_DIR + extra_volumes: list[str] = [] + try: + storage = get_storage() + project_path = get_project_path() + assets_path = storage.get_project_assets_path(project_path) + if assets_path: + assets_str = str(assets_path) + extra_volumes = [ + f"{assets_str}:/app/uploads:ro", + f"{assets_str}:/app/samples:ro", + ] + except Exception: + # Never block tool execution because of asset injection failure + extra_volumes = [] + result = await executor.execute_tool( identifier=identifier, arguments=arguments or {}, timeout=timeout, + extra_volumes=extra_volumes or None, ) return result.to_dict()