forge-cpp-mcp 0.2.3__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/PKG-INFO +32 -5
  2. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/README.md +31 -4
  3. forge_cpp_mcp-0.2.4/frontend/cmake-run-test.html +12 -0
  4. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/package.json +1 -1
  5. forge_cpp_mcp-0.2.4/frontend/src/cmake-run-test.js +8 -0
  6. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/vite.config.js +1 -0
  7. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/hatch_build.py +1 -1
  8. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/pyproject.toml +1 -1
  9. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/__init__.py +1 -1
  10. forge_cpp_mcp-0.2.4/src/forgemcp/cmake/errors.py +5 -0
  11. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/cmake/models.py +13 -0
  12. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/cmake/service.py +95 -2
  13. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/cmake/text.py +19 -0
  14. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/process/service.py +2 -1
  15. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/server.py +1 -0
  16. forge_cpp_mcp-0.2.3/src/forgemcp/cmake/errors.py +0 -5
  17. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/.gitignore +0 -0
  18. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/LICENSE +0 -0
  19. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/clangd-result.html +0 -0
  20. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/cmake-build.html +0 -0
  21. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/cmake-configure.html +0 -0
  22. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/cmake-profiles.html +0 -0
  23. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/cmake-test.html +0 -0
  24. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/package-lock.json +0 -0
  25. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/process-details.html +0 -0
  26. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/process-overview.html +0 -0
  27. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/clangd-navigation-view.js +0 -0
  28. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/clangd-result.js +0 -0
  29. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/clangd-view.js +0 -0
  30. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/cmake-build.js +0 -0
  31. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/cmake-configure.js +0 -0
  32. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/cmake-profiles.js +0 -0
  33. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/cmake-test.js +0 -0
  34. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/cmake-view.js +0 -0
  35. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/process-details.js +0 -0
  36. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/process-overview.js +0 -0
  37. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/process-status.js +0 -0
  38. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/process.css +0 -0
  39. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/shared/app.js +0 -0
  40. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/shared/copy-icon.js +0 -0
  41. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/shared/presentation.js +0 -0
  42. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/shared/result-resources.js +0 -0
  43. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/shared/result-view.js +0 -0
  44. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/shared/widget.css +0 -0
  45. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/source-view.js +0 -0
  46. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/toolsets.js +0 -0
  47. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/workspace-file.js +0 -0
  48. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/workspace-result.js +0 -0
  49. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/workspace-search.js +0 -0
  50. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/workspace-tree.js +0 -0
  51. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/src/workspace-view.js +0 -0
  52. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/tests/result-resources.test.js +0 -0
  53. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/tests/result-view.test.js +0 -0
  54. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/tests/workspace.test.js +0 -0
  55. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/toolsets.html +0 -0
  56. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/workspace-file.html +0 -0
  57. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/workspace-result.html +0 -0
  58. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/workspace-search.html +0 -0
  59. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/frontend/workspace-tree.html +0 -0
  60. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/assets.py +0 -0
  61. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/clangd/__init__.py +0 -0
  62. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/clangd/errors.py +0 -0
  63. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/clangd/models.py +0 -0
  64. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/clangd/service.py +0 -0
  65. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/clangd/session.py +0 -0
  66. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/clangd/text.py +0 -0
  67. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/cmake/__init__.py +0 -0
  68. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/cmake/profiles.py +0 -0
  69. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/completion.py +0 -0
  70. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/icons/clangd.LICENSE +0 -0
  71. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/icons/clangd.svg +0 -0
  72. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/icons/cmake.svg +0 -0
  73. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/icons/process.svg +0 -0
  74. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/icons/toolchain.svg +0 -0
  75. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/icons/workspace-edit.svg +0 -0
  76. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/icons/workspace-file.svg +0 -0
  77. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/icons/workspace-search.svg +0 -0
  78. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/icons/workspace.svg +0 -0
  79. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/markdown.py +0 -0
  80. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/process/__init__.py +0 -0
  81. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/process/errors.py +0 -0
  82. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/process/models.py +0 -0
  83. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/process/text.py +0 -0
  84. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/process/transcript.py +0 -0
  85. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/progress.py +0 -0
  86. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/text.py +0 -0
  87. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/__init__.py +0 -0
  88. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/discovery.py +0 -0
  89. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/errors.py +0 -0
  90. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/loader.py +0 -0
  91. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/providers/__init__.py +0 -0
  92. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/providers/system.py +0 -0
  93. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/providers/user.py +0 -0
  94. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/providers/visual_studio.py +0 -0
  95. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/service.py +0 -0
  96. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/spec.py +0 -0
  97. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/__init__.py +0 -0
  98. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/clang.py +0 -0
  99. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/clang_cl.py +0 -0
  100. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/clangd.py +0 -0
  101. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/clangxx.py +0 -0
  102. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/cmake.py +0 -0
  103. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/cppvsdbg.py +0 -0
  104. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/ctest.py +0 -0
  105. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/gcc.py +0 -0
  106. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/gdb.py +0 -0
  107. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/git.py +0 -0
  108. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/gxx.py +0 -0
  109. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/link.py +0 -0
  110. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/lld.py +0 -0
  111. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/lld_link.py +0 -0
  112. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/lldb_dap.py +0 -0
  113. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/make.py +0 -0
  114. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/msbuild.py +0 -0
  115. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/msvc.py +0 -0
  116. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/toolchain/tools/ninja.py +0 -0
  117. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/workspace/__init__.py +0 -0
  118. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/workspace/diff.py +0 -0
  119. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/workspace/errors.py +0 -0
  120. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/workspace/metadata.py +0 -0
  121. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/workspace/models.py +0 -0
  122. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/workspace/path.py +0 -0
  123. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/workspace/providers.py +0 -0
  124. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/workspace/service.py +0 -0
  125. {forge_cpp_mcp-0.2.3 → forge_cpp_mcp-0.2.4}/src/forgemcp/workspace/text.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: forge-cpp-mcp
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: A safe MCP server for structured C++ project development workflows.
5
5
  Project-URL: Homepage, https://github.com/hardened-steel/ForgeMCP
6
6
  Project-URL: Repository, https://github.com/hardened-steel/ForgeMCP
@@ -44,7 +44,8 @@ debugger modules are planned.
44
44
  configuration selections use all available contexts.
45
45
  - No prompts are currently registered.
46
46
  - CMake tools list operator profiles, configure projects, build targets, and run
47
- CTest. Each tool has its own widget with profile filters, Fields/JSON views,
47
+ CTest. `cmake_run_test` directly runs a project/storage test executable with
48
+ literal command-line arguments. Each tool has its own widget with Fields/JSON views,
48
49
  copying and per-test results. Command logs are available through `process_get`. CMake syntax highlighting remains deferred.
49
50
  - Tool `processes_overview` shows running and completed external development tools,
50
51
  including exit codes and timeout interruptions. `process_get(process_id)` returns
@@ -75,7 +76,9 @@ without making additional tool calls. Process times are readable to seconds; JSO
75
76
  copying preserve the original precision. A toolsets list shows summaries, while a
76
77
  `toolset_get` result shows the details returned by that call.
77
78
 
78
- ForgeMCP does not expose arbitrary command execution over MCP. Feature services use
79
+ ForgeMCP can directly run an explicitly selected test executable inside the
80
+ project or storage root; it does not expose shell commands or external executable
81
+ paths over MCP. Feature services use
79
82
  the shared process service internally; its public MCP surface is read-only.
80
83
 
81
84
  ## Requirements
@@ -93,7 +96,7 @@ and install it in a virtual environment:
93
96
 
94
97
  ```powershell
95
98
  python -m venv .venv
96
- .\.venv\Scripts\python.exe -m pip install .\forge_cpp_mcp-0.2.3-py3-none-any.whl
99
+ .\.venv\Scripts\python.exe -m pip install .\forge_cpp_mcp-0.2.4-py3-none-any.whl
97
100
  .\.venv\Scripts\forgemcp.exe --help
98
101
  ```
99
102
 
@@ -105,7 +108,7 @@ The distribution name is `forge-cpp-mcp`; the Python module and server command a
105
108
  After the package is published to PyPI, the install command can instead be:
106
109
 
107
110
  ```powershell
108
- .\.venv\Scripts\python.exe -m pip install forge-cpp-mcp==0.2.3
111
+ .\.venv\Scripts\python.exe -m pip install forge-cpp-mcp==0.2.4
109
112
  ```
110
113
 
111
114
  ## Setup from source
@@ -332,6 +335,30 @@ Full process transcripts remain in the process module; CMake results contain no
332
335
  transcript links. The default command timeout is 600 seconds per execution and
333
336
  can be overridden with the existing `ProcessTimeout` shape (`total`, `idle`).
334
337
 
338
+ `cmake_run_test(executable, arguments=null, cwd="project/", timeout={"total":600})`
339
+ runs one already-built test executable directly, without a profile or CTest
340
+ registration. Executable and working-directory paths use `project/...` or
341
+ `storage/...` and must resolve inside their respective roots. The working directory
342
+ must exist; storage is not created automatically. On Windows, batch files are not
343
+ supported. Arguments are a list of literal strings, preserving spaces and empty
344
+ strings; NUL is rejected. For example:
345
+
346
+ ```json
347
+ {
348
+ "executable": "project/build/ninja-debug/fixture_tests.exe",
349
+ "arguments": ["--fail"],
350
+ "cwd": "project/build/ninja-debug",
351
+ "timeout": {"total": 30}
352
+ }
353
+ ```
354
+
355
+ The process inherits the server environment, closes stdin, and waits for completion.
356
+ It does not replay toolset/preset environments or CTest properties such as
357
+ `WILL_FAIL`, fixtures, or per-test timeouts. Exit zero means success; a nonzero
358
+ exit or process failure is reported in `error`. The single structured result
359
+ contains `executable`, `arguments`, `cwd`, `error`, and `process_id`; logs are read
360
+ through `process_get`. No JUnit or framework case report is inferred from output.
361
+
335
362
  With no explicit profiles:
336
363
 
337
364
  - If `CMakePresets.json` or `CMakeUserPresets.json` exists, the automatic `presets`
@@ -19,7 +19,8 @@ debugger modules are planned.
19
19
  configuration selections use all available contexts.
20
20
  - No prompts are currently registered.
21
21
  - CMake tools list operator profiles, configure projects, build targets, and run
22
- CTest. Each tool has its own widget with profile filters, Fields/JSON views,
22
+ CTest. `cmake_run_test` directly runs a project/storage test executable with
23
+ literal command-line arguments. Each tool has its own widget with Fields/JSON views,
23
24
  copying and per-test results. Command logs are available through `process_get`. CMake syntax highlighting remains deferred.
24
25
  - Tool `processes_overview` shows running and completed external development tools,
25
26
  including exit codes and timeout interruptions. `process_get(process_id)` returns
@@ -50,7 +51,9 @@ without making additional tool calls. Process times are readable to seconds; JSO
50
51
  copying preserve the original precision. A toolsets list shows summaries, while a
51
52
  `toolset_get` result shows the details returned by that call.
52
53
 
53
- ForgeMCP does not expose arbitrary command execution over MCP. Feature services use
54
+ ForgeMCP can directly run an explicitly selected test executable inside the
55
+ project or storage root; it does not expose shell commands or external executable
56
+ paths over MCP. Feature services use
54
57
  the shared process service internally; its public MCP surface is read-only.
55
58
 
56
59
  ## Requirements
@@ -68,7 +71,7 @@ and install it in a virtual environment:
68
71
 
69
72
  ```powershell
70
73
  python -m venv .venv
71
- .\.venv\Scripts\python.exe -m pip install .\forge_cpp_mcp-0.2.3-py3-none-any.whl
74
+ .\.venv\Scripts\python.exe -m pip install .\forge_cpp_mcp-0.2.4-py3-none-any.whl
72
75
  .\.venv\Scripts\forgemcp.exe --help
73
76
  ```
74
77
 
@@ -80,7 +83,7 @@ The distribution name is `forge-cpp-mcp`; the Python module and server command a
80
83
  After the package is published to PyPI, the install command can instead be:
81
84
 
82
85
  ```powershell
83
- .\.venv\Scripts\python.exe -m pip install forge-cpp-mcp==0.2.3
86
+ .\.venv\Scripts\python.exe -m pip install forge-cpp-mcp==0.2.4
84
87
  ```
85
88
 
86
89
  ## Setup from source
@@ -307,6 +310,30 @@ Full process transcripts remain in the process module; CMake results contain no
307
310
  transcript links. The default command timeout is 600 seconds per execution and
308
311
  can be overridden with the existing `ProcessTimeout` shape (`total`, `idle`).
309
312
 
313
+ `cmake_run_test(executable, arguments=null, cwd="project/", timeout={"total":600})`
314
+ runs one already-built test executable directly, without a profile or CTest
315
+ registration. Executable and working-directory paths use `project/...` or
316
+ `storage/...` and must resolve inside their respective roots. The working directory
317
+ must exist; storage is not created automatically. On Windows, batch files are not
318
+ supported. Arguments are a list of literal strings, preserving spaces and empty
319
+ strings; NUL is rejected. For example:
320
+
321
+ ```json
322
+ {
323
+ "executable": "project/build/ninja-debug/fixture_tests.exe",
324
+ "arguments": ["--fail"],
325
+ "cwd": "project/build/ninja-debug",
326
+ "timeout": {"total": 30}
327
+ }
328
+ ```
329
+
330
+ The process inherits the server environment, closes stdin, and waits for completion.
331
+ It does not replay toolset/preset environments or CTest properties such as
332
+ `WILL_FAIL`, fixtures, or per-test timeouts. Exit zero means success; a nonzero
333
+ exit or process failure is reported in `error`. The single structured result
334
+ contains `executable`, `arguments`, `cwd`, `error`, and `process_id`; logs are read
335
+ through `process_get`. No JUnit or framework case report is inferred from output.
336
+
310
337
  With no explicit profiles:
311
338
 
312
339
  - If `CMakePresets.json` or `CMakeUserPresets.json` exists, the automatic `presets`
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Test executable result</title>
7
+ </head>
8
+ <body>
9
+ <main id="widget" aria-label="Test executable result"></main>
10
+ <script type="module" src="/src/cmake-run-test.js"></script>
11
+ </body>
12
+ </html>
@@ -3,7 +3,7 @@
3
3
  "private": true,
4
4
  "type": "module",
5
5
  "scripts": {
6
- "build": "vite build --mode workspace && vite build --mode file && vite build --mode search && vite build --mode result && vite build --mode process && vite build --mode process-details && vite build --mode toolsets && vite build --mode cmake-profiles && vite build --mode cmake-configure && vite build --mode cmake-build && vite build --mode cmake-test && vite build --mode clangd",
6
+ "build": "vite build --mode workspace && vite build --mode file && vite build --mode search && vite build --mode result && vite build --mode process && vite build --mode process-details && vite build --mode toolsets && vite build --mode cmake-profiles && vite build --mode cmake-configure && vite build --mode cmake-build && vite build --mode cmake-test && vite build --mode cmake-run-test && vite build --mode clangd",
7
7
  "test": "node --test tests/*.test.js"
8
8
  },
9
9
  "dependencies": {
@@ -0,0 +1,8 @@
1
+ import { connectWidget } from "./shared/app.js";
2
+ import { cmakePresentation, renderCMakeValue } from "./cmake-view.js";
3
+
4
+ await connectWidget({
5
+ toolName: "cmake_run_test",
6
+ describe: (data) => cmakePresentation(data, "cmake_run_test"),
7
+ renderValue: renderCMakeValue,
8
+ });
@@ -15,6 +15,7 @@ const widgets = {
15
15
  "cmake-configure": "cmake-configure",
16
16
  "cmake-build": "cmake-build",
17
17
  "cmake-test": "cmake-test",
18
+ "cmake-run-test": "cmake-run-test",
18
19
  clangd: "clangd-result",
19
20
  };
20
21
 
@@ -31,7 +31,7 @@ class CustomBuildHook(BuildHookInterface):
31
31
  Path(self.root) / "src" / "forgemcp" / "assets" / "clangd-result.html",
32
32
  *(
33
33
  Path(self.root) / "src" / "forgemcp" / "assets" / f"cmake-{kind}.html"
34
- for kind in ("profiles", "configure", "build", "test")
34
+ for kind in ("profiles", "configure", "build", "test", "run-test")
35
35
  ),
36
36
  ]
37
37
  artifacts = build_data.setdefault("artifacts", [])
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "forge-cpp-mcp"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  description = "A safe MCP server for structured C++ project development workflows."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.2.3"
5
+ __version__ = "0.2.4"
@@ -0,0 +1,5 @@
1
+ """Expected CMake configuration and operation errors."""
2
+
3
+
4
+ class CMakeError(Exception):
5
+ """Invalid profiles, execution paths, unavailable tools, or unusable build metadata."""
@@ -72,3 +72,16 @@ class CMakeTestResult(BaseModel):
72
72
  description="Null on success; failure explanation otherwise.",
73
73
  )
74
74
  process_id: int | None = None
75
+
76
+
77
+ class CMakeRunTestResult(BaseModel):
78
+ """One direct executable run with its command inputs, error, and process reference."""
79
+
80
+ executable: WorkspacePath
81
+ arguments: list[str] = Field(default_factory=list)
82
+ cwd: WorkspacePath
83
+ error: str | None = Field(
84
+ default=None,
85
+ description="Null on success; failure explanation otherwise.",
86
+ )
87
+ process_id: int | None = None
@@ -20,8 +20,9 @@ from pydantic import Field
20
20
 
21
21
  from forgemcp.assets import IconFile, Widget
22
22
  from forgemcp.completion import Complete
23
- from forgemcp.process.errors import ProcessError
23
+ from forgemcp.process.errors import ProcessError, ProcessStartError
24
24
  from forgemcp.process.models import ProcessTimeout
25
+ from forgemcp.process.service import ProcessService
25
26
  from forgemcp.progress import progress
26
27
  from forgemcp.text import tool_result
27
28
  from forgemcp.toolchain.errors import ToolchainError
@@ -38,8 +39,9 @@ from .models import (
38
39
  CMakeConfigureResult,
39
40
  CMakeBuildResult,
40
41
  CMakeTestResult,
42
+ CMakeRunTestResult,
41
43
  )
42
- from .text import render_executions, render_profiles
44
+ from .text import render_executions, render_profiles, render_run_test
43
45
 
44
46
 
45
47
  class CMakeService:
@@ -57,6 +59,11 @@ class CMakeService:
57
59
  Configure before building; build before testing. Diagnose with a subset,
58
60
  then verify all profiles. Each execution reports error (null means success)
59
61
  and, when launched, process_id for its command log.
62
+
63
+ cmake_run_test runs an explicit project/storage test executable with literal
64
+ arguments and a working directory, inheriting the server environment.
65
+ It requires no profile or CTest registration. Zero exit means success;
66
+ CTest properties, preset environments, and framework case reports do not apply.
60
67
  """
61
68
 
62
69
  ICON = IconFile("icons/cmake.svg")
@@ -64,10 +71,12 @@ class CMakeService:
64
71
  CONFIGURE_WIDGET = Widget("assets/cmake-configure.html")
65
72
  BUILD_WIDGET = Widget("assets/cmake-build.html")
66
73
  TEST_WIDGET = Widget("assets/cmake-test.html")
74
+ RUN_TEST_WIDGET = Widget("assets/cmake-run-test.html")
67
75
 
68
76
  def __init__(
69
77
  self,
70
78
  toolchains: ToolchainService,
79
+ processes: ProcessService,
71
80
  profiles: tuple[ProfileDefinition, ...] = (),
72
81
  *,
73
82
  project_root: Path,
@@ -81,6 +90,7 @@ class CMakeService:
81
90
  self.storage_root = storage_root.resolve()
82
91
  self.protected_paths = protected_paths
83
92
  self.toolchains = toolchains
93
+ self.processes = processes
84
94
  self.definitions = profiles
85
95
  self.default_toolset = default_toolset
86
96
  self.progress_interval = progress_interval
@@ -477,6 +487,23 @@ class CMakeService:
477
487
  queue.put_nowait(snapshot)
478
488
  self.configuration_subscribers[queue] = signature
479
489
 
490
+ def execution_path(self, reference: WorkspacePath) -> Path:
491
+ """Resolve a test executable or working directory without escaping its managed root."""
492
+ if reference.area == "root":
493
+ raise CMakeError("Test execution paths must be inside project/ or storage/.")
494
+ base = self.project_root if reference.area == "project" else self.storage_root
495
+ try:
496
+ if os.name == "nt" and any(
497
+ os.path.isreserved(part) for part in Path(reference.relative).parts
498
+ ):
499
+ raise CMakeError(f"Test execution path contains a reserved name: {reference}.")
500
+ path = (base / reference.relative).resolve()
501
+ if not path.is_relative_to(base):
502
+ raise CMakeError(f"Test execution path resolves outside its root: {reference}.")
503
+ return path
504
+ except OSError as error:
505
+ raise CMakeError(f"Cannot resolve test execution path: {reference}.") from error
506
+
480
507
  def register(self, mcp: MCPServer, apps: Apps, complete: Complete) -> None:
481
508
  """Register profile inspection, configure, build, and test tools with their widgets."""
482
509
  icon = self.ICON.icon
@@ -862,10 +889,76 @@ class CMakeService:
862
889
  await report_status(result.error or "Completed successfully")
863
890
  return tool_result(results, render_executions(results, "CTest"))
864
891
 
892
+ @apps.tool(
893
+ resource_uri=self.RUN_TEST_WIDGET.uri,
894
+ icons=[icon],
895
+ annotations=changes_files,
896
+ )
897
+ async def cmake_run_test(
898
+ executable: WorkspacePath,
899
+ ctx: Context,
900
+ arguments: list[str] | None = None,
901
+ cwd: WorkspacePath = WorkspacePath("project/"),
902
+ timeout: ProcessTimeout = ProcessTimeout(total=600),
903
+ ) -> Annotated[CallToolResult, CMakeRunTestResult]:
904
+ """Run one project/storage test executable directly with literal command-line arguments.
905
+
906
+ Inherits the server environment; cwd defaults to project/. Zero exit
907
+ means success. No profiles, CTest properties, or case reports are used.
908
+ Read output through process_get with the returned process_id.
909
+ """
910
+ report = progress(ctx, interval=self.progress_interval)
911
+ await report(0, message="Preparing test executable")
912
+ values = arguments if arguments is not None else []
913
+ if any("\0" in value for value in values):
914
+ raise ToolError("Test arguments cannot contain NUL.")
915
+ try:
916
+ program = self.execution_path(executable)
917
+ directory = self.execution_path(cwd)
918
+ if not program.is_file():
919
+ raise CMakeError(f"Test executable is not a file: {executable}.")
920
+ if not directory.is_dir():
921
+ raise CMakeError(f"Test working directory does not exist: {cwd}.")
922
+ if os.name == "nt" and program.suffix.lower() in (".bat", ".cmd"):
923
+ raise CMakeError("Test executable must be a native program, not a batch file.")
924
+ except CMakeError as error:
925
+ raise ToolError(str(error)) from error
926
+ except OSError as error:
927
+ raise ToolError("Cannot inspect test executable or working directory.") from error
928
+ result = CMakeRunTestResult(
929
+ executable=executable,
930
+ arguments=values,
931
+ cwd=cwd,
932
+ )
933
+ updates = 1
934
+ await report(updates, message="Starting test executable")
935
+ try:
936
+ async with await self.processes.launch(
937
+ program,
938
+ values,
939
+ cwd=directory,
940
+ timeout=timeout,
941
+ ) as session:
942
+ result.process_id = session.process_id
943
+ await session.close_stdin()
944
+ async for _ in session.output():
945
+ updates += 1
946
+ await report(updates, message="Receiving test output")
947
+ code = await session.wait()
948
+ if code != 0:
949
+ result.error = f"Test executable exited with {code}."
950
+ except ProcessStartError:
951
+ result.error = f"Cannot start test executable: {executable}."
952
+ except ProcessError as error:
953
+ result.error = str(error)
954
+ await report(updates + 1, message=result.error or "Completed successfully")
955
+ return tool_result(result, render_run_test(result))
956
+
865
957
  for widget in (
866
958
  self.PROFILES_WIDGET,
867
959
  self.CONFIGURE_WIDGET,
868
960
  self.BUILD_WIDGET,
869
961
  self.TEST_WIDGET,
962
+ self.RUN_TEST_WIDGET,
870
963
  ):
871
964
  apps.add_html_resource(widget.uri, widget.content)
@@ -1,5 +1,6 @@
1
1
  """Plain-text CMake settings, execution outcomes, and complete CTest cases."""
2
2
 
3
+ import json
3
4
  from typing import Literal
4
5
 
5
6
  from forgemcp.toolchain.tools import ctest
@@ -9,6 +10,7 @@ from .models import (
9
10
  CMakeConfigureResult,
10
11
  CMakeBuildResult,
11
12
  CMakeTestResult,
13
+ CMakeRunTestResult,
12
14
  )
13
15
 
14
16
 
@@ -96,3 +98,20 @@ def render_test_cases(tests: list[ctest.TestCase]) -> list[str]:
96
98
  if test.message is not None:
97
99
  lines.append(" " + test.message.replace("\n", "\n "))
98
100
  return lines
101
+
102
+
103
+ def render_run_test(result: CMakeRunTestResult) -> str:
104
+ """Describe a direct run and preserve argument boundaries without copying its transcript."""
105
+ lines = [
106
+ f"Test executable: {result.executable}",
107
+ f" Arguments: {json.dumps(result.arguments, ensure_ascii=False)}",
108
+ f" Working directory: {result.cwd}",
109
+ f" Outcome: {'Succeeded' if result.error is None else 'Failed'}",
110
+ ]
111
+ if result.error is not None:
112
+ lines.append(" Error: " + result.error.replace("\n", "\n "))
113
+ if result.process_id is not None:
114
+ lines.append(f" Command output: process_get(process_id={result.process_id})")
115
+ else:
116
+ lines.append(" Process: not available")
117
+ return "\n".join(lines)
@@ -324,7 +324,8 @@ class ProcessService:
324
324
 
325
325
  Feature results identify commands by process_id. Process state and ordered
326
326
  stdin/stdout/stderr logs are retained in memory until the server stops.
327
- Arbitrary command execution is unavailable through the public surface.
327
+ This service exposes no command execution tools. cmake_run_test can launch an
328
+ explicit test executable inside project/storage through the CMake surface.
328
329
  """
329
330
 
330
331
  WIDGET = Widget("assets/process-overview.html")
@@ -63,6 +63,7 @@ def create_server(
63
63
  )
64
64
  builds = CMakeService(
65
65
  toolchains,
66
+ processes,
66
67
  profiles,
67
68
  project_root=workspace.root,
68
69
  storage_root=workspace.storage_root,
@@ -1,5 +0,0 @@
1
- """Expected CMake configuration and operation errors."""
2
-
3
-
4
- class CMakeError(Exception):
5
- """Invalid profiles, unavailable tools, or unusable CMake build metadata."""
File without changes
File without changes