From b448ceda926e1e618397834af63932f3976d1df2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:45:02 -0700 Subject: [PATCH 1/7] Bump torch from 2.10.0 to 2.13.0 in /mcp-local (#123) Bumps [torch](https://github.com/pytorch/pytorch) from 2.10.0 to 2.13.0. - [Release notes](https://github.com/pytorch/pytorch/releases) - [Changelog](https://github.com/pytorch/pytorch/blob/main/RELEASE.md) - [Commits](https://github.com/pytorch/pytorch/compare/v2.10.0...v2.13.0) --- updated-dependencies: - dependency-name: torch dependency-version: 2.13.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mcp-local/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcp-local/requirements.txt b/mcp-local/requirements.txt index 0a6675b..c7b596a 100644 --- a/mcp-local/requirements.txt +++ b/mcp-local/requirements.txt @@ -1,4 +1,4 @@ -torch==2.10.0 +torch==2.13.0 usearch==2.26.0 # USearch leaves NumKong unconstrained, so pin it explicitly for reproducible wheel installs. numkong==7.7.0 From c3aacc151cfb99418cdab64cc96d7d9feec9e8a4 Mon Sep 17 00:00:00 2001 From: Jason Andrews Date: Mon, 13 Jul 2026 15:28:19 -0500 Subject: [PATCH 2/7] Add logging for arm mcp server testing --- mcp-local/server.py | 8 ++- mcp-local/tests/test_invocation_logger.py | 51 +++++++++++++++++ mcp-local/utils/invocation_logger.py | 70 ++++++++++++++++++----- 3 files changed, 111 insertions(+), 18 deletions(-) create mode 100644 mcp-local/tests/test_invocation_logger.py diff --git a/mcp-local/server.py b/mcp-local/server.py index b08e6d5..54d2671 100644 --- a/mcp-local/server.py +++ b/mcp-local/server.py @@ -28,7 +28,7 @@ from utils.migrate_ease_utils import run_migrate_ease_scan from utils.skopeo_tool import skopeo_help, skopeo_inspect from utils.llvm_mca_tool import mca_help, llvm_mca_analyze -from utils.invocation_logger import log_invocation_reason +from utils.invocation_logger import log_invocation_reason, log_tool_result from utils.error_handling import format_tool_error # Initialize the MCP server @@ -52,7 +52,7 @@ ) def knowledge_base_search(query: str, invocation_reason: Optional[str] = None) -> List[Dict[str, Any]]: # Log invocation reason if provided - log_invocation_reason( + entry_id = log_invocation_reason( tool="knowledge_base_search", reason=invocation_reason, args={"query": query}, @@ -67,7 +67,9 @@ def knowledge_base_search(query: str, invocation_reason: Optional[str] = None) - List of dictionaries with metadata including url and text snippets. """ try: - return arm_kb_search.search(query, SEARCH_RESOURCES) + results = arm_kb_search.search(query, SEARCH_RESOURCES) + log_tool_result(entry_id, "knowledge_base_search", results) + return results except Exception as e: return format_tool_error( tool="knowledge_base_search", diff --git a/mcp-local/tests/test_invocation_logger.py b/mcp-local/tests/test_invocation_logger.py new file mode 100644 index 0000000..76c66bd --- /dev/null +++ b/mcp-local/tests/test_invocation_logger.py @@ -0,0 +1,51 @@ +import json + +from utils import invocation_logger + + +def test_logs_paired_call_and_result(tmp_path, monkeypatch): + traffic_path = tmp_path / "mcp-traffic.jsonl" + monkeypatch.setattr(invocation_logger, "WORKSPACE_DIR", str(tmp_path)) + monkeypatch.setenv(invocation_logger.MCP_TRAFFIC_LOG_ENV, str(traffic_path)) + + entry_id = invocation_logger.log_invocation_reason( + tool="knowledge_base_search", + reason="Need current Arm documentation", + args={"query": "SME overview"}, + ) + result = [{"title": "SME guide", "score": 0.9}] + invocation_logger.log_tool_result(entry_id, "knowledge_base_search", result) + + entries = [json.loads(line) for line in traffic_path.read_text().splitlines()] + assert entries == [ + { + "id": entry_id, + "timestamp": entries[0]["timestamp"], + "tool": "knowledge_base_search", + "args": {"query": "SME overview"}, + "invocation_reason": "Need current Arm documentation", + }, + { + "id": entry_id, + "type": "result", + "tool": "knowledge_base_search", + "result": result, + }, + ] + + +def test_logs_call_without_invocation_reason(tmp_path, monkeypatch): + traffic_path = tmp_path / "mcp-traffic.jsonl" + monkeypatch.setattr(invocation_logger, "WORKSPACE_DIR", str(tmp_path)) + monkeypatch.setenv(invocation_logger.MCP_TRAFFIC_LOG_ENV, str(traffic_path)) + + entry_id = invocation_logger.log_invocation_reason( + tool="knowledge_base_search", + reason=None, + args={"query": "SVE2"}, + ) + + entry = json.loads(traffic_path.read_text()) + assert entry["id"] == entry_id + assert entry["invocation_reason"] is None + assert not (tmp_path / invocation_logger.LOG_FILE_NAME).exists() diff --git a/mcp-local/utils/invocation_logger.py b/mcp-local/utils/invocation_logger.py index 64d2e6c..3bfa4cf 100644 --- a/mcp-local/utils/invocation_logger.py +++ b/mcp-local/utils/invocation_logger.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import os import uuid from datetime import datetime, timezone @@ -23,38 +24,77 @@ LOG_FILE_NAME = "invocation_reasons.yaml" +MCP_TRAFFIC_LOG_ENV = "MCP_LOG_FILE" +MCP_TRAFFIC_LOG_DEFAULT = "/workspace/mcp-traffic.jsonl" def _now_iso() -> str: return datetime.now(timezone.utc).isoformat() -def log_invocation_reason(tool: str, reason: Optional[str], args: Optional[Dict[str, Any]] = None) -> None: +def log_invocation_reason( + tool: str, + reason: Optional[str], + args: Optional[Dict[str, Any]] = None, +) -> str: """ Append a YAML document with the tool invocation reason and metadata to /workspace/invocation_reasons.yaml. + Also append a JSONL call entry to MCP_LOG_FILE. - Each call writes a separate YAML document with fields: id, timestamp, tool, args, reason. + Returns the entry ID so the caller can pair the tool result with this invocation. Errors are swallowed to avoid impacting tool execution. """ - if not reason: - return + entry_id = str(uuid.uuid4()) + timestamp = _now_iso() - entry = { - "id": str(uuid.uuid4()), - "timestamp": _now_iso(), + if reason: + entry = { + "id": entry_id, + "timestamp": timestamp, + "tool": tool, + "args": args or {}, + "reason": str(reason), + } + + log_path = os.path.join(WORKSPACE_DIR, LOG_FILE_NAME) + + try: + os.makedirs(WORKSPACE_DIR, exist_ok=True) + with open(log_path, "a", encoding="utf-8") as f: + yaml.safe_dump(entry, f, explicit_start=True, sort_keys=False, allow_unicode=True) + except Exception: + pass + + traffic_entry = { + "id": entry_id, + "timestamp": timestamp, "tool": tool, "args": args or {}, - "reason": str(reason), + "invocation_reason": reason, } + traffic_path = os.environ.get(MCP_TRAFFIC_LOG_ENV, MCP_TRAFFIC_LOG_DEFAULT) + try: + os.makedirs(os.path.dirname(traffic_path) or WORKSPACE_DIR, exist_ok=True) + with open(traffic_path, "a", encoding="utf-8") as f: + f.write(json.dumps(traffic_entry) + "\n") + except Exception: + pass + + return entry_id - log_path = os.path.join(WORKSPACE_DIR, LOG_FILE_NAME) +def log_tool_result(entry_id: str, tool: str, result: Any) -> None: + """Append a JSONL result entry paired with a tool invocation.""" + traffic_path = os.environ.get(MCP_TRAFFIC_LOG_ENV, MCP_TRAFFIC_LOG_DEFAULT) + result_entry = { + "id": entry_id, + "type": "result", + "tool": tool, + "result": result, + } try: - # Ensure workspace directory exists (it should in runtime environments) - os.makedirs(WORKSPACE_DIR, exist_ok=True) - with open(log_path, "a", encoding="utf-8") as f: - yaml.safe_dump(entry, f, explicit_start=True, sort_keys=False, allow_unicode=True) + os.makedirs(os.path.dirname(traffic_path) or WORKSPACE_DIR, exist_ok=True) + with open(traffic_path, "a", encoding="utf-8") as f: + f.write(json.dumps(result_entry, default=str) + "\n") except Exception: - # Do not break tool execution if logging fails pass - From 45bed4064187ee5af0d8228e8b59ed7569058015 Mon Sep 17 00:00:00 2001 From: Brian King Date: Wed, 29 Jul 2026 17:05:18 -0500 Subject: [PATCH 3/7] Remove invocation-reasons.yaml --- .github/workflows/integration-tests.yml | 17 ++++++++++ .gitignore | 4 ++- README.md | 15 +++++++++ mcp-local/server.py | 2 +- mcp-local/tests/constants.py | 2 +- mcp-local/tests/test_invocation_logger.py | 6 ++-- mcp-local/utils/error_handling.py | 2 +- mcp-local/utils/invocation_logger.py | 39 +++++------------------ mcp-local/utils/migrate_ease_utils.py | 2 ++ 9 files changed, 51 insertions(+), 38 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 6c9d900..e9317db 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -5,6 +5,23 @@ on: workflow_dispatch: jobs: + unit-tests: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + + - name: Install unit test dependencies + run: python -m pip install pytest + + - name: Run unit tests + run: python -m pytest -q mcp-local/tests/test_invocation_logger.py + integration-tests: strategy: fail-fast: false diff --git a/.gitignore b/.gitignore index 5025e4b..3ab765b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# MCP server logs +mcp-traffic.jsonl +error_logging.yaml invocation_reasons.yaml # Virtual environments @@ -37,4 +40,3 @@ embedding-generation/intrinsic_chunks/*.yaml embedding-generation/*.txt embedding-generation/metadata.json embedding-generation/usearch_index.bin - diff --git a/README.md b/README.md index 43c6951..e655434 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,21 @@ args = [ After updating the configuration, restart your MCP client to load the Arm MCP server. +## Logging + +By default, the server creates two log files in `/workspace`. With the +configuration examples above, these files appear in the project directory on +your computer: + +- `mcp-traffic.jsonl` records when tools are used, the inputs provided, and the + reason for each tool call. It also records results from knowledge base + searches. +- `error_logging.yaml` records details about errors encountered by the server. + This information can help with troubleshooting. + +These logs may contain information from your project and tool requests. Review +their contents before sharing them. + ## Repository Structure - **`mcp-local/`**: The MCP server implementation diff --git a/mcp-local/server.py b/mcp-local/server.py index 54d2671..bccd176 100644 --- a/mcp-local/server.py +++ b/mcp-local/server.py @@ -51,7 +51,7 @@ description="If a user asks to migrate a codebase to Arm, strongly consider using this tool as a part of your strategy. Searches an Arm knowledge base of learning resources, Arm intrinsics, and software version compatibility using semantic similarity. Given a natural language query, returns a list of matching resources with URLs, titles, and content snippets, ranked by relevance. Useful for finding documentation, tutorials, or version compatibility for Arm migrations. Returned URLs may include tracking query parameters such as utm_source=arm-mcp and URL fragments. When sharing or citing returned URLs, preserve each URL exactly as returned, including query parameters and fragments; do not remove, normalize, shorten, or rewrite them. Includes 'invocation_reason' parameter so the model can briefly explain why it is calling this tool to provide additional context." ) def knowledge_base_search(query: str, invocation_reason: Optional[str] = None) -> List[Dict[str, Any]]: - # Log invocation reason if provided + # Log the call and retain its ID for the paired search result. entry_id = log_invocation_reason( tool="knowledge_base_search", reason=invocation_reason, diff --git a/mcp-local/tests/constants.py b/mcp-local/tests/constants.py index 8c30074..1479d29 100644 --- a/mcp-local/tests/constants.py +++ b/mcp-local/tests/constants.py @@ -131,7 +131,7 @@ "output_file": "/tmp/migrate_ease_java_20260126-215207.json", "output_format": "json", "workspace_listing": [ - "invocation_reasons.yaml" + "mcp-traffic.jsonl" ], "excluded_items": [], "excluded_count": 0, diff --git a/mcp-local/tests/test_invocation_logger.py b/mcp-local/tests/test_invocation_logger.py index 76c66bd..ce01c9a 100644 --- a/mcp-local/tests/test_invocation_logger.py +++ b/mcp-local/tests/test_invocation_logger.py @@ -1,12 +1,14 @@ import json +import sys +from pathlib import Path +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from utils import invocation_logger def test_logs_paired_call_and_result(tmp_path, monkeypatch): traffic_path = tmp_path / "mcp-traffic.jsonl" monkeypatch.setattr(invocation_logger, "WORKSPACE_DIR", str(tmp_path)) - monkeypatch.setenv(invocation_logger.MCP_TRAFFIC_LOG_ENV, str(traffic_path)) entry_id = invocation_logger.log_invocation_reason( tool="knowledge_base_search", @@ -37,7 +39,6 @@ def test_logs_paired_call_and_result(tmp_path, monkeypatch): def test_logs_call_without_invocation_reason(tmp_path, monkeypatch): traffic_path = tmp_path / "mcp-traffic.jsonl" monkeypatch.setattr(invocation_logger, "WORKSPACE_DIR", str(tmp_path)) - monkeypatch.setenv(invocation_logger.MCP_TRAFFIC_LOG_ENV, str(traffic_path)) entry_id = invocation_logger.log_invocation_reason( tool="knowledge_base_search", @@ -48,4 +49,3 @@ def test_logs_call_without_invocation_reason(tmp_path, monkeypatch): entry = json.loads(traffic_path.read_text()) assert entry["id"] == entry_id assert entry["invocation_reason"] is None - assert not (tmp_path / invocation_logger.LOG_FILE_NAME).exists() diff --git a/mcp-local/utils/error_handling.py b/mcp-local/utils/error_handling.py index 6fb3e95..3c18b86 100644 --- a/mcp-local/utils/error_handling.py +++ b/mcp-local/utils/error_handling.py @@ -70,6 +70,6 @@ def format_tool_error(tool: str, exc: Exception, args: Optional[Dict[str, Any]] "traceback": traceback.format_exc(), "args": args or {}, "guidance": ( - "Use this information to suggest fixes to the user. Also add that if the user is experiencing problems that are due to the MCP server itself, they can email mcpserver@arm.com. There is a file called invocation_reasons.yaml located in the /workspace container filesystem mount that records why the model decided to invoke a particular tool, and a file called error_logging.yaml that records recent errors. The user can also send these files to mcpserver@arm.com for added helpful troubleshooting context." + "Use this information to suggest fixes to the user. Also add that if the user is experiencing problems that are due to the MCP server itself, they can email mcpserver@arm.com. There is a file called mcp-traffic.jsonl located in the /workspace container filesystem mount that records tool calls and selected tool results, and a file called error_logging.yaml that records recent errors. The user can also send these files to mcpserver@arm.com for added helpful troubleshooting context." ), } diff --git a/mcp-local/utils/invocation_logger.py b/mcp-local/utils/invocation_logger.py index 3bfa4cf..34d4371 100644 --- a/mcp-local/utils/invocation_logger.py +++ b/mcp-local/utils/invocation_logger.py @@ -18,14 +18,10 @@ from datetime import datetime, timezone from typing import Optional, Dict, Any -import yaml - from .config import WORKSPACE_DIR -LOG_FILE_NAME = "invocation_reasons.yaml" -MCP_TRAFFIC_LOG_ENV = "MCP_LOG_FILE" -MCP_TRAFFIC_LOG_DEFAULT = "/workspace/mcp-traffic.jsonl" +LOG_FILE_NAME = "mcp-traffic.jsonl" def _now_iso() -> str: @@ -38,8 +34,7 @@ def log_invocation_reason( args: Optional[Dict[str, Any]] = None, ) -> str: """ - Append a YAML document with the tool invocation reason and metadata to /workspace/invocation_reasons.yaml. - Also append a JSONL call entry to MCP_LOG_FILE. + Append a JSONL call entry to the workspace traffic log. Returns the entry ID so the caller can pair the tool result with this invocation. Errors are swallowed to avoid impacting tool execution. @@ -47,24 +42,6 @@ def log_invocation_reason( entry_id = str(uuid.uuid4()) timestamp = _now_iso() - if reason: - entry = { - "id": entry_id, - "timestamp": timestamp, - "tool": tool, - "args": args or {}, - "reason": str(reason), - } - - log_path = os.path.join(WORKSPACE_DIR, LOG_FILE_NAME) - - try: - os.makedirs(WORKSPACE_DIR, exist_ok=True) - with open(log_path, "a", encoding="utf-8") as f: - yaml.safe_dump(entry, f, explicit_start=True, sort_keys=False, allow_unicode=True) - except Exception: - pass - traffic_entry = { "id": entry_id, "timestamp": timestamp, @@ -72,10 +49,10 @@ def log_invocation_reason( "args": args or {}, "invocation_reason": reason, } - traffic_path = os.environ.get(MCP_TRAFFIC_LOG_ENV, MCP_TRAFFIC_LOG_DEFAULT) + log_path = os.path.join(WORKSPACE_DIR, LOG_FILE_NAME) try: - os.makedirs(os.path.dirname(traffic_path) or WORKSPACE_DIR, exist_ok=True) - with open(traffic_path, "a", encoding="utf-8") as f: + os.makedirs(WORKSPACE_DIR, exist_ok=True) + with open(log_path, "a", encoding="utf-8") as f: f.write(json.dumps(traffic_entry) + "\n") except Exception: pass @@ -85,7 +62,7 @@ def log_invocation_reason( def log_tool_result(entry_id: str, tool: str, result: Any) -> None: """Append a JSONL result entry paired with a tool invocation.""" - traffic_path = os.environ.get(MCP_TRAFFIC_LOG_ENV, MCP_TRAFFIC_LOG_DEFAULT) + log_path = os.path.join(WORKSPACE_DIR, LOG_FILE_NAME) result_entry = { "id": entry_id, "type": "result", @@ -93,8 +70,8 @@ def log_tool_result(entry_id: str, tool: str, result: Any) -> None: "result": result, } try: - os.makedirs(os.path.dirname(traffic_path) or WORKSPACE_DIR, exist_ok=True) - with open(traffic_path, "a", encoding="utf-8") as f: + os.makedirs(WORKSPACE_DIR, exist_ok=True) + with open(log_path, "a", encoding="utf-8") as f: f.write(json.dumps(result_entry, default=str) + "\n") except Exception: pass diff --git a/mcp-local/utils/migrate_ease_utils.py b/mcp-local/utils/migrate_ease_utils.py index ee1ca97..125d801 100644 --- a/mcp-local/utils/migrate_ease_utils.py +++ b/mcp-local/utils/migrate_ease_utils.py @@ -35,6 +35,8 @@ '.git', '.svn', '.hg', # IDE and editor directories '.vscode', '.idea', '.eclipse', + # MCP server logs + 'mcp-traffic.jsonl', 'error_logging.yaml', 'invocation_reasons.yaml', # Other common build/cache directories 'target', 'out', '.cache', } From d2826490c75f60b1356063c5a57c6a4e8df6b5db Mon Sep 17 00:00:00 2001 From: Brian King <144239315+brikin01@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:11:59 -0500 Subject: [PATCH 4/7] Remove old filename from migrate_ease_utils Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- mcp-local/utils/migrate_ease_utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mcp-local/utils/migrate_ease_utils.py b/mcp-local/utils/migrate_ease_utils.py index 125d801..a6635d7 100644 --- a/mcp-local/utils/migrate_ease_utils.py +++ b/mcp-local/utils/migrate_ease_utils.py @@ -35,9 +35,7 @@ '.git', '.svn', '.hg', # IDE and editor directories '.vscode', '.idea', '.eclipse', - # MCP server logs - 'mcp-traffic.jsonl', 'error_logging.yaml', 'invocation_reasons.yaml', - # Other common build/cache directories + 'mcp-traffic.jsonl', 'error_logging.yaml', 'target', 'out', '.cache', } From 59d8d13559b3cf9fb447870f50497579b1d7c66b Mon Sep 17 00:00:00 2001 From: Brian King <144239315+brikin01@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:12:15 -0500 Subject: [PATCH 5/7] Sharpen README language Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e655434..72a44f9 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ After updating the configuration, restart your MCP client to load the Arm MCP se ## Logging -By default, the server creates two log files in `/workspace`. With the +Depending on usage, the server may write two log files under `/workspace`. With the configuration examples above, these files appear in the project directory on your computer: From 31839186811e145a27eb25d43a12ceeb5980bcd5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 10:23:12 +0000 Subject: [PATCH 6/7] chore: bump server.json to 2.9.0 Automated release metadata bump for armlimited/arm-mcp:2.9.0. --- mcp-local/server.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mcp-local/server.json b/mcp-local/server.json index c9bd431..11bd051 100644 --- a/mcp-local/server.json +++ b/mcp-local/server.json @@ -3,7 +3,7 @@ "name": "io.github.arm/arm-mcp", "title": "Arm MCP Server", "description": "Official Arm MCP server for code migration, optimization, and Arm architecture guidance", - "version": "2.8.0", + "version": "2.9.0", "repository": { "url": "https://github.com/arm/mcp", "source": "github" @@ -12,7 +12,7 @@ "packages": [ { "registryType": "oci", - "identifier": "docker.io/armlimited/arm-mcp:2.8.0", + "identifier": "docker.io/armlimited/arm-mcp:2.9.0", "runtimeHint": "docker", "transport": { "type": "stdio" From 3626e4c34ae303730a6f11d590275f8de0198a3c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 9 Aug 2026 09:53:40 +0000 Subject: [PATCH 7/7] chore: bump server.json to 2.10.0 Automated release metadata bump for armlimited/arm-mcp:2.10.0. --- mcp-local/server.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mcp-local/server.json b/mcp-local/server.json index 11bd051..92f5eec 100644 --- a/mcp-local/server.json +++ b/mcp-local/server.json @@ -3,7 +3,7 @@ "name": "io.github.arm/arm-mcp", "title": "Arm MCP Server", "description": "Official Arm MCP server for code migration, optimization, and Arm architecture guidance", - "version": "2.9.0", + "version": "2.10.0", "repository": { "url": "https://github.com/arm/mcp", "source": "github" @@ -12,7 +12,7 @@ "packages": [ { "registryType": "oci", - "identifier": "docker.io/armlimited/arm-mcp:2.9.0", + "identifier": "docker.io/armlimited/arm-mcp:2.10.0", "runtimeHint": "docker", "transport": { "type": "stdio"