From ac278ea04c94baf72f05a2d0064a6de71caa225b Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Fri, 28 Nov 2025 17:10:07 +0000 Subject: [PATCH 01/10] Add utility for generating data directories. --- pyproject.toml | 1 + .../mcp_servers/codeql/mcp_server.py | 3 +- .../mcp_servers/logbook/logbook.py | 4 +-- .../mcp_servers/memcache/memcache.py | 3 +- src/seclab_taskflow_agent/path_utils.py | 30 +++++++++++++++++++ 5 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 src/seclab_taskflow_agent/path_utils.py diff --git a/pyproject.toml b/pyproject.toml index 5b5ac7a..4b4790c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ dependencies = [ "parse==1.20.2", "parso==0.8.4", "pathable==0.4.4", + "platformdirs==4.5.0", "pluggy==1.6.0", "pycparser==2.23", "pydantic==2.11.7", diff --git a/src/seclab_taskflow_agent/mcp_servers/codeql/mcp_server.py b/src/seclab_taskflow_agent/mcp_servers/codeql/mcp_server.py index d692bdc..331ac67 100644 --- a/src/seclab_taskflow_agent/mcp_servers/codeql/mcp_server.py +++ b/src/seclab_taskflow_agent/mcp_servers/codeql/mcp_server.py @@ -20,10 +20,11 @@ import re from urllib.parse import urlparse, unquote import zipfile +from seclab_taskflow_agent.path_utils import mcp_data_dir mcp = FastMCP("CodeQL") -CODEQL_DBS_BASE_PATH = Path(os.getenv('CODEQL_DBS_BASE_PATH', default='/workspaces')) +CODEQL_DBS_BASE_PATH = mcp_data_dir('seclab-taskflow-agent', 'codeql', 'CODEQL_DBS_BASE_PATH') # tool name -> templated query lookup for supported languages TEMPLATED_QUERY_PATHS = { diff --git a/src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py b/src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py index 67b8c8f..09c519c 100644 --- a/src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py +++ b/src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py @@ -13,13 +13,13 @@ import json from pathlib import Path import os +from seclab_taskflow_agent.path_utils import mcp_data_dir mcp = FastMCP("Logbook") LOG = {} -LOGBOOK = Path(__file__).parent.resolve() / Path(os.getenv('LOGBOOK_STATE_DIR', default='./')) / Path("logbook.json") - +LOGBOOK = mcp_data_dir('seclab-taskflow-agent', 'memcache', 'LOGBOOK_STATE_DIR') / Path("logbook.json") def ensure_log(): global LOG diff --git a/src/seclab_taskflow_agent/mcp_servers/memcache/memcache.py b/src/seclab_taskflow_agent/mcp_servers/memcache/memcache.py index 1a527fb..6d2d882 100644 --- a/src/seclab_taskflow_agent/mcp_servers/memcache/memcache.py +++ b/src/seclab_taskflow_agent/mcp_servers/memcache/memcache.py @@ -16,6 +16,7 @@ from typing import Any from .memcache_backend.dictionary_file import MemcacheDictionaryFileBackend from .memcache_backend.sqlite import SqliteBackend +from seclab_taskflow_agent.path_utils import mcp_data_dir mcp = FastMCP("Memcache") @@ -27,7 +28,7 @@ # if MEMCACHE_STATE_DIR contains an absolute path we WANT the user to be able # to override the relative path in that case this path join will return # /MEMCACHE_STATE_DIR/memory.json -MEMORY = Path(__file__).parent.resolve() / Path(os.getenv('MEMCACHE_STATE_DIR', default='./')) +MEMORY = mcp_data_dir('seclab-taskflow-agent', 'memcache', 'MEMCACHE_STATE_DIR') BACKEND = os.getenv('MEMCACHE_BACKEND', default='sqlite') backend = backends.get(BACKEND)(str(MEMORY)) diff --git a/src/seclab_taskflow_agent/path_utils.py b/src/seclab_taskflow_agent/path_utils.py new file mode 100644 index 0000000..26a5192 --- /dev/null +++ b/src/seclab_taskflow_agent/path_utils.py @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2025 GitHub +# SPDX-License-Identifier: MIT + +import logging +import platformdirs +import os +from pathlib import Path + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) + +def mcp_data_dir(packagename: str, mcpname: str, env_override: str | None): + """ + Create a directory for an MCP to store its data. + env_override is the name of an environment variable that + can be used to override the default location. + """ + if env_override: + p = os.getenv(env_override) + if p: + return Path(p) + # Use [platformdirs](https://pypi.org/project/platformdirs/) to + # choose an appropriate location. + d = platformdirs.user_data_dir(appname = "seclab-taskflow-agent", + appauthor = "GitHubSecurityLab", + ensure_exists = True) + # Each MCP server gets its own sub-directory + p = Path(d).joinpath(packagename).joinpath(mcpname) + p.mkdir(parents = True, exist_ok = True) + return p From 8783088ed8150a72f224896ca806e4c518301ef7 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Fri, 28 Nov 2025 17:49:35 +0000 Subject: [PATCH 02/10] Update src/seclab_taskflow_agent/path_utils.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/seclab_taskflow_agent/path_utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/seclab_taskflow_agent/path_utils.py b/src/seclab_taskflow_agent/path_utils.py index 26a5192..f2aff9d 100644 --- a/src/seclab_taskflow_agent/path_utils.py +++ b/src/seclab_taskflow_agent/path_utils.py @@ -6,8 +6,6 @@ import os from pathlib import Path -logging.basicConfig(level=logging.DEBUG) -logger = logging.getLogger(__name__) def mcp_data_dir(packagename: str, mcpname: str, env_override: str | None): """ From 41843beca2b846e7f331f9c7b5987f80c6985fa1 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Fri, 28 Nov 2025 17:49:54 +0000 Subject: [PATCH 03/10] Update src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py b/src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py index 09c519c..3f835fe 100644 --- a/src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py +++ b/src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py @@ -19,7 +19,7 @@ LOG = {} -LOGBOOK = mcp_data_dir('seclab-taskflow-agent', 'memcache', 'LOGBOOK_STATE_DIR') / Path("logbook.json") +LOGBOOK = mcp_data_dir('seclab-taskflow-agent', 'logbook', 'LOGBOOK_STATE_DIR') / Path("logbook.json") def ensure_log(): global LOG From b8f4a5dc450245e98f6016a97a27c82c86da6820 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 11:03:03 +0000 Subject: [PATCH 04/10] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/seclab_taskflow_agent/path_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/seclab_taskflow_agent/path_utils.py b/src/seclab_taskflow_agent/path_utils.py index f2aff9d..913cf9e 100644 --- a/src/seclab_taskflow_agent/path_utils.py +++ b/src/seclab_taskflow_agent/path_utils.py @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2025 GitHub # SPDX-License-Identifier: MIT -import logging import platformdirs import os from pathlib import Path From 8329d425e0af1d58ec628925461061971dca4ffd Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 12:24:12 +0000 Subject: [PATCH 05/10] Update src/seclab_taskflow_agent/path_utils.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/seclab_taskflow_agent/path_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seclab_taskflow_agent/path_utils.py b/src/seclab_taskflow_agent/path_utils.py index 913cf9e..4892e4a 100644 --- a/src/seclab_taskflow_agent/path_utils.py +++ b/src/seclab_taskflow_agent/path_utils.py @@ -23,5 +23,5 @@ def mcp_data_dir(packagename: str, mcpname: str, env_override: str | None): ensure_exists = True) # Each MCP server gets its own sub-directory p = Path(d).joinpath(packagename).joinpath(mcpname) - p.mkdir(parents = True, exist_ok = True) + p.mkdir(parents=True, exist_ok=True) return p From 222a5c201de93ac390b40ef9cdff857565433e36 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 12:24:32 +0000 Subject: [PATCH 06/10] Update src/seclab_taskflow_agent/path_utils.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/seclab_taskflow_agent/path_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seclab_taskflow_agent/path_utils.py b/src/seclab_taskflow_agent/path_utils.py index 4892e4a..d1a0a23 100644 --- a/src/seclab_taskflow_agent/path_utils.py +++ b/src/seclab_taskflow_agent/path_utils.py @@ -6,7 +6,7 @@ from pathlib import Path -def mcp_data_dir(packagename: str, mcpname: str, env_override: str | None): +def mcp_data_dir(packagename: str, mcpname: str, env_override: str | None) -> Path: """ Create a directory for an MCP to store its data. env_override is the name of an environment variable that From 460ebae2342e36aa813aee7b77415b2f6046a76e Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 12:42:42 +0000 Subject: [PATCH 07/10] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py b/src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py index 3f835fe..8c8bec0 100644 --- a/src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py +++ b/src/seclab_taskflow_agent/mcp_servers/logbook/logbook.py @@ -12,7 +12,6 @@ from fastmcp import FastMCP # move to FastMCP 2.0 import json from pathlib import Path -import os from seclab_taskflow_agent.path_utils import mcp_data_dir mcp = FastMCP("Logbook") From e8513b316f6ae69a0c2ab2e557217d196cb9982d Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 12:43:07 +0000 Subject: [PATCH 08/10] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/seclab_taskflow_agent/mcp_servers/memcache/memcache.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/seclab_taskflow_agent/mcp_servers/memcache/memcache.py b/src/seclab_taskflow_agent/mcp_servers/memcache/memcache.py index 6d2d882..f8b706c 100644 --- a/src/seclab_taskflow_agent/mcp_servers/memcache/memcache.py +++ b/src/seclab_taskflow_agent/mcp_servers/memcache/memcache.py @@ -25,9 +25,6 @@ 'sqlite': SqliteBackend, } -# if MEMCACHE_STATE_DIR contains an absolute path we WANT the user to be able -# to override the relative path in that case this path join will return -# /MEMCACHE_STATE_DIR/memory.json MEMORY = mcp_data_dir('seclab-taskflow-agent', 'memcache', 'MEMCACHE_STATE_DIR') BACKEND = os.getenv('MEMCACHE_BACKEND', default='sqlite') From d1d0afb649480cec0e0f164f406c10353b28b793 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 12:44:19 +0000 Subject: [PATCH 09/10] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/seclab_taskflow_agent/path_utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/seclab_taskflow_agent/path_utils.py b/src/seclab_taskflow_agent/path_utils.py index d1a0a23..f2b65c3 100644 --- a/src/seclab_taskflow_agent/path_utils.py +++ b/src/seclab_taskflow_agent/path_utils.py @@ -9,8 +9,14 @@ def mcp_data_dir(packagename: str, mcpname: str, env_override: str | None) -> Path: """ Create a directory for an MCP to store its data. - env_override is the name of an environment variable that - can be used to override the default location. + + Parameters: + packagename (str): The name of the package. Used as a subdirectory under the data directory. + mcpname (str): The name of the MCP server. Used as a subdirectory under the package directory. + env_override (str | None): The name of an environment variable that, if set, overrides the default data directory location. If None, the default location is used. + + Returns: + Path: The path to the created data directory for the MCP server. """ if env_override: p = os.getenv(env_override) From 96d9cc197fe05de49dfaa2d67e403d558ad1e2b6 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 12:44:52 +0000 Subject: [PATCH 10/10] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/seclab_taskflow_agent/path_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/seclab_taskflow_agent/path_utils.py b/src/seclab_taskflow_agent/path_utils.py index f2b65c3..1175191 100644 --- a/src/seclab_taskflow_agent/path_utils.py +++ b/src/seclab_taskflow_agent/path_utils.py @@ -24,9 +24,9 @@ def mcp_data_dir(packagename: str, mcpname: str, env_override: str | None) -> Pa return Path(p) # Use [platformdirs](https://pypi.org/project/platformdirs/) to # choose an appropriate location. - d = platformdirs.user_data_dir(appname = "seclab-taskflow-agent", - appauthor = "GitHubSecurityLab", - ensure_exists = True) + d = platformdirs.user_data_dir(appname="seclab-taskflow-agent", + appauthor="GitHubSecurityLab", + ensure_exists=True) # Each MCP server gets its own sub-directory p = Path(d).joinpath(packagename).joinpath(mcpname) p.mkdir(parents=True, exist_ok=True)