forge-cpp-mcp 0.2.1__tar.gz → 0.2.3__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.
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/PKG-INFO +55 -13
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/README.md +53 -12
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/shared/app.js +2 -1
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/shared/result-resources.js +14 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/shared/result-view.js +1 -1
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/workspace-search.js +1 -1
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/workspace-view.js +12 -7
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/hatch_build.py +1 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/pyproject.toml +2 -1
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/__init__.py +1 -1
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/assets.py +3 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/clangd/errors.py +1 -0
- forge_cpp_mcp-0.2.3/src/forgemcp/clangd/models.py +186 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/clangd/service.py +204 -110
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/clangd/session.py +72 -44
- forge_cpp_mcp-0.2.3/src/forgemcp/clangd/text.py +244 -0
- forge_cpp_mcp-0.2.3/src/forgemcp/cmake/models.py +74 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/cmake/profiles.py +3 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/cmake/service.py +43 -67
- forge_cpp_mcp-0.2.3/src/forgemcp/cmake/text.py +98 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/completion.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/markdown.py +44 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/process/models.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/process/service.py +36 -18
- forge_cpp_mcp-0.2.3/src/forgemcp/process/text.py +68 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/process/transcript.py +2 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/progress.py +3 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/server.py +1 -0
- forge_cpp_mcp-0.2.3/src/forgemcp/text.py +37 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/discovery.py +1 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/errors.py +1 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/loader.py +3 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/providers/system.py +2 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/providers/user.py +5 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/providers/visual_studio.py +5 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/service.py +55 -4
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/spec.py +7 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/clang.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/clang_cl.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/clangd.py +26 -1
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/clangxx.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/cmake.py +35 -3
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/cppvsdbg.py +2 -1
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/ctest.py +16 -1
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/gcc.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/gdb.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/git.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/gxx.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/link.py +2 -1
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/lld.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/lld_link.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/lldb_dap.py +2 -1
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/make.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/msbuild.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/msvc.py +2 -1
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/ninja.py +4 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/workspace/diff.py +14 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/workspace/metadata.py +1 -0
- forge_cpp_mcp-0.2.3/src/forgemcp/workspace/models.py +103 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/workspace/path.py +18 -1
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/workspace/providers.py +6 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/workspace/service.py +174 -108
- forge_cpp_mcp-0.2.3/src/forgemcp/workspace/text.py +113 -0
- forge_cpp_mcp-0.2.1/src/forgemcp/clangd/models.py +0 -94
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/.gitignore +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/LICENSE +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/clangd-result.html +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/cmake-build.html +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/cmake-configure.html +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/cmake-profiles.html +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/cmake-test.html +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/package-lock.json +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/package.json +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/process-details.html +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/process-overview.html +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/clangd-navigation-view.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/clangd-result.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/clangd-view.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/cmake-build.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/cmake-configure.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/cmake-profiles.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/cmake-test.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/cmake-view.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/process-details.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/process-overview.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/process-status.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/process.css +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/shared/copy-icon.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/shared/presentation.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/shared/widget.css +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/source-view.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/toolsets.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/workspace-file.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/workspace-result.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/src/workspace-tree.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/tests/result-resources.test.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/tests/result-view.test.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/tests/workspace.test.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/toolsets.html +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/vite.config.js +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/workspace-file.html +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/workspace-result.html +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/workspace-search.html +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/frontend/workspace-tree.html +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/clangd/__init__.py +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/cmake/__init__.py +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/cmake/errors.py +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/icons/clangd.LICENSE +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/icons/clangd.svg +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/icons/cmake.svg +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/icons/process.svg +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/icons/toolchain.svg +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/icons/workspace-edit.svg +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/icons/workspace-file.svg +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/icons/workspace-search.svg +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/icons/workspace.svg +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/process/__init__.py +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/process/errors.py +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/__init__.py +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/providers/__init__.py +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/toolchain/tools/__init__.py +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/workspace/__init__.py +0 -0
- {forge_cpp_mcp-0.2.1 → forge_cpp_mcp-0.2.3}/src/forgemcp/workspace/errors.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: forge-cpp-mcp
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
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
|
|
@@ -10,6 +10,7 @@ Author: ForgeMCP contributors
|
|
|
10
10
|
License-Expression: MIT
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Requires-Python: >=3.13
|
|
13
|
+
Requires-Dist: markdown-it-py>=4.0
|
|
13
14
|
Requires-Dist: mcp[cli]==2.1.1
|
|
14
15
|
Requires-Dist: pydantic>=2.13
|
|
15
16
|
Requires-Dist: regex>=2024.11.6
|
|
@@ -56,8 +57,12 @@ debugger modules are planned.
|
|
|
56
57
|
toolsets as markdown, querying available versions for details, with completion
|
|
57
58
|
for retained toolset IDs.
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
Every tool returns readable English plain text in `content` alongside its typed
|
|
61
|
+
`structuredContent`. Trees use branch characters, source excerpts use line numbers,
|
|
62
|
+
and diagnostics use compiler-style messages. Text does not include Markdown
|
|
63
|
+
formatting or Workspace provider resource links; the structured data retains those
|
|
64
|
+
links for widgets. Clients decide how to display the text and may prefer structured
|
|
65
|
+
data when supplying results to a model.
|
|
61
66
|
|
|
62
67
|
All widgets share a compact console style with a fixed 420px height and adapt to the
|
|
63
68
|
host width. The move confirmation uses only two rows, source and destination.
|
|
@@ -88,7 +93,7 @@ and install it in a virtual environment:
|
|
|
88
93
|
|
|
89
94
|
```powershell
|
|
90
95
|
python -m venv .venv
|
|
91
|
-
.\.venv\Scripts\python.exe -m pip install .\forge_cpp_mcp-0.2.
|
|
96
|
+
.\.venv\Scripts\python.exe -m pip install .\forge_cpp_mcp-0.2.3-py3-none-any.whl
|
|
92
97
|
.\.venv\Scripts\forgemcp.exe --help
|
|
93
98
|
```
|
|
94
99
|
|
|
@@ -100,7 +105,7 @@ The distribution name is `forge-cpp-mcp`; the Python module and server command a
|
|
|
100
105
|
After the package is published to PyPI, the install command can instead be:
|
|
101
106
|
|
|
102
107
|
```powershell
|
|
103
|
-
.\.venv\Scripts\python.exe -m pip install forge-cpp-mcp==0.2.
|
|
108
|
+
.\.venv\Scripts\python.exe -m pip install forge-cpp-mcp==0.2.3
|
|
104
109
|
```
|
|
105
110
|
|
|
106
111
|
## Setup from source
|
|
@@ -163,13 +168,27 @@ add the `forgemcp` entry to it rather than replacing the file. Restart the clien
|
|
|
163
168
|
after editing its configuration. Check the connection with `codex mcp list` or
|
|
164
169
|
`claude mcp get forgemcp`, respectively.
|
|
165
170
|
|
|
171
|
+
To require agents to use ForgeMCP for supported operations, append the
|
|
172
|
+
[example agent instructions](examples/mcp-clients/agent-instructions.md) to your
|
|
173
|
+
project's `AGENTS.md` (Codex) or `CLAUDE.md` (Claude Code). The same block works
|
|
174
|
+
for both clients and requires evidence of an unsupported operation, tool failure,
|
|
175
|
+
or unavailability before using alternatives. Preserve existing project instructions.
|
|
176
|
+
|
|
166
177
|
## Workspace files and storage
|
|
167
178
|
|
|
168
179
|
All file tools use string paths such as `project/src/main.cpp` and
|
|
169
180
|
`storage/build/debug`. Directory tools default to `project/`; the separate tool
|
|
170
181
|
argument `root` has been removed. Python consumers use the shared `WorkspacePath`
|
|
171
|
-
type, which serializes as the same string.
|
|
172
|
-
|
|
182
|
+
type, which serializes as the same string. Tool path arguments also accept ordinary
|
|
183
|
+
relative paths: `src/main.cpp` and `./src/main.cpp` both mean `project/src/main.cpp`,
|
|
184
|
+
and `./` means `project/`. One trailing `/` is accepted for managed paths:
|
|
185
|
+
`project/src/` becomes `project/src`. Results always use canonical qualified paths.
|
|
186
|
+
The prefixes `project/`, `storage/`, and `root/` are reserved; use `./storage/...`
|
|
187
|
+
to access a project directory named `storage`. Absolute paths, `..`, and repeated
|
|
188
|
+
separators remain invalid; external locations require an explicit `root/` prefix.
|
|
189
|
+
|
|
190
|
+
Storage defaults to `.<project-name>.forgemcp` beside the project. Set its location
|
|
191
|
+
explicitly when needed:
|
|
173
192
|
|
|
174
193
|
```powershell
|
|
175
194
|
forgemcp --workspace C:\Projects\Example --workspace-storage D:\ForgeMCP\Example
|
|
@@ -192,7 +211,7 @@ a working-directory check, not an operating-system sandbox.
|
|
|
192
211
|
| `workspace_find_files(pattern="*", path="project/")` | Recursive filename/path glob search |
|
|
193
212
|
| `workspace_file_info(path)` | Creation/modification times, byte size, owner; unavailable metadata is null |
|
|
194
213
|
| `workspace_read_file(path, start_line=1, end_line=null)` | UTF-8 text, with an optional inclusive line range |
|
|
195
|
-
| `
|
|
214
|
+
| `workspace_text_search(query, path="project/", regex=false, extensions=null, case_sensitive=true, max_matches=100)` | Bounded matching lines, exact counts, and a linked skipped-file list |
|
|
196
215
|
| `workspace_write_file(path, text)` | Create or overwrite; return removed/added line counts |
|
|
197
216
|
| `workspace_edit_file(path, old_text, new_text, replace_all=false)` | Exact replacement; zero or ambiguous matches fail without modifying the file |
|
|
198
217
|
| `workspace_move(source, destination)` | Move a file or directory; destination must not exist |
|
|
@@ -209,8 +228,15 @@ dot-prefixed files remain visible. Searches skip directories beginning with a do
|
|
|
209
228
|
access hidden directories and in-root links. Mutations cannot traverse links;
|
|
210
229
|
moving/removing a whole tree containing links is rejected. Dependent modules may
|
|
211
230
|
register protected paths, which remain readable but cannot be changed through the
|
|
212
|
-
workspace API. Searches
|
|
213
|
-
|
|
231
|
+
workspace API. Searches have no application-level timeout or pagination. Text search
|
|
232
|
+
returns at most `max_matches` matching lines (positive integer, default 100), with
|
|
233
|
+
all spans on each returned line. It continues scanning to return exact
|
|
234
|
+
`matches_count` and `skipped_files_count`; `matches_truncated` indicates omitted
|
|
235
|
+
matching lines. The complete binary/non-UTF-8 file list is an immutable JSON resource
|
|
236
|
+
linked as `resources.skipped_files` when nonempty, rather than inline output.
|
|
237
|
+
The widget loads that snapshot for its skipped-files tab. Narrow the path/extensions
|
|
238
|
+
or increase `max_matches` to retrieve more matching lines. Individual lines and their
|
|
239
|
+
span arrays have no byte limit.
|
|
214
240
|
|
|
215
241
|
File resource templates use a single path including its project/ or storage/ prefix:
|
|
216
242
|
|
|
@@ -220,7 +246,7 @@ forgemcp://workspace/raw{/path*}
|
|
|
220
246
|
forgemcp://workspace/list{?path,depth,include_hidden}
|
|
221
247
|
forgemcp://workspace/find-files{?pattern,path}
|
|
222
248
|
forgemcp://workspace/file-info{?path}
|
|
223
|
-
forgemcp://workspace/search{?query,path,regex,extensions,case_sensitive}
|
|
249
|
+
forgemcp://workspace/search{?query,path,regex,extensions,case_sensitive,max_matches}
|
|
224
250
|
forgemcp://workspace/results/{result_id}/{name}.json
|
|
225
251
|
forgemcp://workspace/results/{result_id}/{name}.md
|
|
226
252
|
```
|
|
@@ -254,10 +280,26 @@ A resource-loading failure does not change the successful file-operation outcome
|
|
|
254
280
|
When a CMake configuration has a compilation database and clangd in its toolset,
|
|
255
281
|
Workspace reads, writes, and edits of C/C++ files also return
|
|
256
282
|
`resources.clangd`. This immutable JSON contains diagnostics and semantic
|
|
257
|
-
highlighting grouped by configuration
|
|
258
|
-
|
|
283
|
+
highlighting grouped by configuration, always across all available configurations.
|
|
284
|
+
Clangd analyzes the complete file even for a partial Workspace read, but the saved
|
|
285
|
+
diagnostics and semantic spans include only those intersecting the lines actually
|
|
286
|
+
returned. Their coordinates remain absolute file positions; an empty partial-read
|
|
287
|
+
excerpt (for example, beyond EOF) has no annotations.
|
|
288
|
+
|
|
259
289
|
Clangd starts sessions from CMake's configuration subscription and keeps them until
|
|
260
290
|
the configuration disappears, its database changes, or the server stops.
|
|
291
|
+
Each process receives `-j=2`; this worker setting does not impose a memory limit.
|
|
292
|
+
File analysis opens the complete current file, collects the requested result, and
|
|
293
|
+
closes the document before returning. Documents are not retained between calls, so
|
|
294
|
+
the next analysis incorporates saved header changes without reopening other files
|
|
295
|
+
after each Workspace mutation. Repeated calls pay the cost of a fresh file parse;
|
|
296
|
+
closing a document does not release clangd's whole index. Workspace mutations still
|
|
297
|
+
notify sessions of filesystem changes and reconcile changed compilation databases.
|
|
298
|
+
Explicit clangd tools use their existing `configurations` argument to select a
|
|
299
|
+
subset, for example
|
|
300
|
+
`clangd_diagnostics(path="project/src/main.cpp", configurations=["debug"])`.
|
|
301
|
+
Omitting that argument or passing `[]` selects all available configurations.
|
|
302
|
+
|
|
261
303
|
Workspace widgets load both resources independently. The source view lets you
|
|
262
304
|
select a configuration for semantic highlighting and inline diagnostic markers;
|
|
263
305
|
lexical C/C++ colors also cover keywords, comments, literals, directives, and nested
|
|
@@ -32,8 +32,12 @@ debugger modules are planned.
|
|
|
32
32
|
toolsets as markdown, querying available versions for details, with completion
|
|
33
33
|
for retained toolset IDs.
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
Every tool returns readable English plain text in `content` alongside its typed
|
|
36
|
+
`structuredContent`. Trees use branch characters, source excerpts use line numbers,
|
|
37
|
+
and diagnostics use compiler-style messages. Text does not include Markdown
|
|
38
|
+
formatting or Workspace provider resource links; the structured data retains those
|
|
39
|
+
links for widgets. Clients decide how to display the text and may prefer structured
|
|
40
|
+
data when supplying results to a model.
|
|
37
41
|
|
|
38
42
|
All widgets share a compact console style with a fixed 420px height and adapt to the
|
|
39
43
|
host width. The move confirmation uses only two rows, source and destination.
|
|
@@ -64,7 +68,7 @@ and install it in a virtual environment:
|
|
|
64
68
|
|
|
65
69
|
```powershell
|
|
66
70
|
python -m venv .venv
|
|
67
|
-
.\.venv\Scripts\python.exe -m pip install .\forge_cpp_mcp-0.2.
|
|
71
|
+
.\.venv\Scripts\python.exe -m pip install .\forge_cpp_mcp-0.2.3-py3-none-any.whl
|
|
68
72
|
.\.venv\Scripts\forgemcp.exe --help
|
|
69
73
|
```
|
|
70
74
|
|
|
@@ -76,7 +80,7 @@ The distribution name is `forge-cpp-mcp`; the Python module and server command a
|
|
|
76
80
|
After the package is published to PyPI, the install command can instead be:
|
|
77
81
|
|
|
78
82
|
```powershell
|
|
79
|
-
.\.venv\Scripts\python.exe -m pip install forge-cpp-mcp==0.2.
|
|
83
|
+
.\.venv\Scripts\python.exe -m pip install forge-cpp-mcp==0.2.3
|
|
80
84
|
```
|
|
81
85
|
|
|
82
86
|
## Setup from source
|
|
@@ -139,13 +143,27 @@ add the `forgemcp` entry to it rather than replacing the file. Restart the clien
|
|
|
139
143
|
after editing its configuration. Check the connection with `codex mcp list` or
|
|
140
144
|
`claude mcp get forgemcp`, respectively.
|
|
141
145
|
|
|
146
|
+
To require agents to use ForgeMCP for supported operations, append the
|
|
147
|
+
[example agent instructions](examples/mcp-clients/agent-instructions.md) to your
|
|
148
|
+
project's `AGENTS.md` (Codex) or `CLAUDE.md` (Claude Code). The same block works
|
|
149
|
+
for both clients and requires evidence of an unsupported operation, tool failure,
|
|
150
|
+
or unavailability before using alternatives. Preserve existing project instructions.
|
|
151
|
+
|
|
142
152
|
## Workspace files and storage
|
|
143
153
|
|
|
144
154
|
All file tools use string paths such as `project/src/main.cpp` and
|
|
145
155
|
`storage/build/debug`. Directory tools default to `project/`; the separate tool
|
|
146
156
|
argument `root` has been removed. Python consumers use the shared `WorkspacePath`
|
|
147
|
-
type, which serializes as the same string.
|
|
148
|
-
|
|
157
|
+
type, which serializes as the same string. Tool path arguments also accept ordinary
|
|
158
|
+
relative paths: `src/main.cpp` and `./src/main.cpp` both mean `project/src/main.cpp`,
|
|
159
|
+
and `./` means `project/`. One trailing `/` is accepted for managed paths:
|
|
160
|
+
`project/src/` becomes `project/src`. Results always use canonical qualified paths.
|
|
161
|
+
The prefixes `project/`, `storage/`, and `root/` are reserved; use `./storage/...`
|
|
162
|
+
to access a project directory named `storage`. Absolute paths, `..`, and repeated
|
|
163
|
+
separators remain invalid; external locations require an explicit `root/` prefix.
|
|
164
|
+
|
|
165
|
+
Storage defaults to `.<project-name>.forgemcp` beside the project. Set its location
|
|
166
|
+
explicitly when needed:
|
|
149
167
|
|
|
150
168
|
```powershell
|
|
151
169
|
forgemcp --workspace C:\Projects\Example --workspace-storage D:\ForgeMCP\Example
|
|
@@ -168,7 +186,7 @@ a working-directory check, not an operating-system sandbox.
|
|
|
168
186
|
| `workspace_find_files(pattern="*", path="project/")` | Recursive filename/path glob search |
|
|
169
187
|
| `workspace_file_info(path)` | Creation/modification times, byte size, owner; unavailable metadata is null |
|
|
170
188
|
| `workspace_read_file(path, start_line=1, end_line=null)` | UTF-8 text, with an optional inclusive line range |
|
|
171
|
-
| `
|
|
189
|
+
| `workspace_text_search(query, path="project/", regex=false, extensions=null, case_sensitive=true, max_matches=100)` | Bounded matching lines, exact counts, and a linked skipped-file list |
|
|
172
190
|
| `workspace_write_file(path, text)` | Create or overwrite; return removed/added line counts |
|
|
173
191
|
| `workspace_edit_file(path, old_text, new_text, replace_all=false)` | Exact replacement; zero or ambiguous matches fail without modifying the file |
|
|
174
192
|
| `workspace_move(source, destination)` | Move a file or directory; destination must not exist |
|
|
@@ -185,8 +203,15 @@ dot-prefixed files remain visible. Searches skip directories beginning with a do
|
|
|
185
203
|
access hidden directories and in-root links. Mutations cannot traverse links;
|
|
186
204
|
moving/removing a whole tree containing links is rejected. Dependent modules may
|
|
187
205
|
register protected paths, which remain readable but cannot be changed through the
|
|
188
|
-
workspace API. Searches
|
|
189
|
-
|
|
206
|
+
workspace API. Searches have no application-level timeout or pagination. Text search
|
|
207
|
+
returns at most `max_matches` matching lines (positive integer, default 100), with
|
|
208
|
+
all spans on each returned line. It continues scanning to return exact
|
|
209
|
+
`matches_count` and `skipped_files_count`; `matches_truncated` indicates omitted
|
|
210
|
+
matching lines. The complete binary/non-UTF-8 file list is an immutable JSON resource
|
|
211
|
+
linked as `resources.skipped_files` when nonempty, rather than inline output.
|
|
212
|
+
The widget loads that snapshot for its skipped-files tab. Narrow the path/extensions
|
|
213
|
+
or increase `max_matches` to retrieve more matching lines. Individual lines and their
|
|
214
|
+
span arrays have no byte limit.
|
|
190
215
|
|
|
191
216
|
File resource templates use a single path including its project/ or storage/ prefix:
|
|
192
217
|
|
|
@@ -196,7 +221,7 @@ forgemcp://workspace/raw{/path*}
|
|
|
196
221
|
forgemcp://workspace/list{?path,depth,include_hidden}
|
|
197
222
|
forgemcp://workspace/find-files{?pattern,path}
|
|
198
223
|
forgemcp://workspace/file-info{?path}
|
|
199
|
-
forgemcp://workspace/search{?query,path,regex,extensions,case_sensitive}
|
|
224
|
+
forgemcp://workspace/search{?query,path,regex,extensions,case_sensitive,max_matches}
|
|
200
225
|
forgemcp://workspace/results/{result_id}/{name}.json
|
|
201
226
|
forgemcp://workspace/results/{result_id}/{name}.md
|
|
202
227
|
```
|
|
@@ -230,10 +255,26 @@ A resource-loading failure does not change the successful file-operation outcome
|
|
|
230
255
|
When a CMake configuration has a compilation database and clangd in its toolset,
|
|
231
256
|
Workspace reads, writes, and edits of C/C++ files also return
|
|
232
257
|
`resources.clangd`. This immutable JSON contains diagnostics and semantic
|
|
233
|
-
highlighting grouped by configuration
|
|
234
|
-
|
|
258
|
+
highlighting grouped by configuration, always across all available configurations.
|
|
259
|
+
Clangd analyzes the complete file even for a partial Workspace read, but the saved
|
|
260
|
+
diagnostics and semantic spans include only those intersecting the lines actually
|
|
261
|
+
returned. Their coordinates remain absolute file positions; an empty partial-read
|
|
262
|
+
excerpt (for example, beyond EOF) has no annotations.
|
|
263
|
+
|
|
235
264
|
Clangd starts sessions from CMake's configuration subscription and keeps them until
|
|
236
265
|
the configuration disappears, its database changes, or the server stops.
|
|
266
|
+
Each process receives `-j=2`; this worker setting does not impose a memory limit.
|
|
267
|
+
File analysis opens the complete current file, collects the requested result, and
|
|
268
|
+
closes the document before returning. Documents are not retained between calls, so
|
|
269
|
+
the next analysis incorporates saved header changes without reopening other files
|
|
270
|
+
after each Workspace mutation. Repeated calls pay the cost of a fresh file parse;
|
|
271
|
+
closing a document does not release clangd's whole index. Workspace mutations still
|
|
272
|
+
notify sessions of filesystem changes and reconcile changed compilation databases.
|
|
273
|
+
Explicit clangd tools use their existing `configurations` argument to select a
|
|
274
|
+
subset, for example
|
|
275
|
+
`clangd_diagnostics(path="project/src/main.cpp", configurations=["debug"])`.
|
|
276
|
+
Omitting that argument or passing `[]` selects all available configurations.
|
|
277
|
+
|
|
237
278
|
Workspace widgets load both resources independently. The source view lets you
|
|
238
279
|
select a configuration for semantic highlighting and inline diagnostic markers;
|
|
239
280
|
lexical C/C++ colors also cover keywords, comments, literals, directives, and nested
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { App, PostMessageTransport, applyDocumentTheme, applyHostFonts, applyHostStyleVariables } from "@modelcontextprotocol/ext-apps";
|
|
2
2
|
import { createResultView } from "./result-view.js";
|
|
3
|
-
import { loadResultClangd, loadResultDiff } from "./result-resources.js";
|
|
3
|
+
import { loadResultClangd, loadResultDiff, loadResultSkippedFiles } from "./result-resources.js";
|
|
4
4
|
import "./widget.css";
|
|
5
5
|
|
|
6
6
|
/** Lifecycle bridge and immutable result-resource loading; never calls tools. */
|
|
@@ -34,6 +34,7 @@ export async function connectWidget({ toolName, describe, renderValue }) {
|
|
|
34
34
|
if (result?.isError || data?.action === "moved") return;
|
|
35
35
|
await Promise.all([
|
|
36
36
|
["diff", loadResultDiff], ["clangd", loadResultClangd],
|
|
37
|
+
["skipped_files", loadResultSkippedFiles],
|
|
37
38
|
].filter(([name]) => data?.resources?.[name]).map(async ([name, load]) => {
|
|
38
39
|
try {
|
|
39
40
|
const resource = await load(result, (params) => app.readServerResource(params), () => current === generation);
|
|
@@ -47,6 +47,20 @@ function resultResourceUri(link, provider) {
|
|
|
47
47
|
return link.uri;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
export async function loadResultSkippedFiles(result, read, current = () => true) {
|
|
51
|
+
const link = result?.structuredContent?.resources?.skipped_files;
|
|
52
|
+
if (!link || result.isError) return null;
|
|
53
|
+
const uri = resultResourceUri(link, "skipped_files");
|
|
54
|
+
if (!current()) return null;
|
|
55
|
+
const data = decode(await read({ uri }), uri);
|
|
56
|
+
if (!Array.isArray(data?.skipped_files) || !data.skipped_files.every((path) =>
|
|
57
|
+
typeof path === "string" && /^(project|storage)\//.test(path))
|
|
58
|
+
|| data.skipped_files.length !== result.structuredContent.skipped_files_count) {
|
|
59
|
+
throw new Error("Invalid skipped-file resource");
|
|
60
|
+
}
|
|
61
|
+
return current() ? data : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
50
64
|
export async function loadResultClangd(result, read, current = () => true) {
|
|
51
65
|
const link = result?.structuredContent?.resources?.clangd;
|
|
52
66
|
if (!link || result.isError) return null;
|
|
@@ -272,7 +272,7 @@ export function createResultView(root, { toolName, describe, renderValue }) {
|
|
|
272
272
|
copyResource.addEventListener("click", () => { void copy(JSON.stringify(resource, null, 2)); });
|
|
273
273
|
heading.append(copyResource);
|
|
274
274
|
content.append(heading, json);
|
|
275
|
-
} else if (["diff", "clangd"].includes(name)) {
|
|
275
|
+
} else if (["diff", "clangd", "skipped_files"].includes(name)) {
|
|
276
276
|
content.append(element("p", "fm-empty", `${name}: ${resourceData[`${name}State`] === "error" ? "Resource could not be loaded." : "Loading resource…"}`));
|
|
277
277
|
}
|
|
278
278
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { connectWidget } from "./shared/app.js";
|
|
2
2
|
import { workspacePresentation, workspaceValue } from "./workspace-view.js";
|
|
3
3
|
|
|
4
|
-
await connectWidget({ toolName: "
|
|
4
|
+
await connectWidget({ toolName: "workspace_text_search", describe: workspacePresentation, renderValue: workspaceValue });
|
|
@@ -3,7 +3,7 @@ import { isObject } from "./shared/presentation.js";
|
|
|
3
3
|
import { clangdValue } from "./clangd-view.js";
|
|
4
4
|
import { appendSourceText, cppSyntax, sourceConfiguration } from "./source-view.js";
|
|
5
5
|
|
|
6
|
-
function searchView(doc, data, query, width) {
|
|
6
|
+
function searchView(doc, data, query, width, loaded) {
|
|
7
7
|
const node = (tag, className, text) => {
|
|
8
8
|
const result = doc.createElement(tag);
|
|
9
9
|
result.className = className;
|
|
@@ -17,7 +17,7 @@ function searchView(doc, data, query, width) {
|
|
|
17
17
|
const matches = node("div", "fm-search-matches");
|
|
18
18
|
const skipped = node("div", "fm-search-skipped");
|
|
19
19
|
const panels = [matches, skipped];
|
|
20
|
-
const buttons = ["Matches", `Skipped files (${data.
|
|
20
|
+
const buttons = ["Matches", `Skipped files (${data.skipped_files_count})`].map((title, index) => {
|
|
21
21
|
const button = node("button", "", title);
|
|
22
22
|
button.type = "button";
|
|
23
23
|
button.id = `workspace-search-tab-${index}`;
|
|
@@ -127,9 +127,14 @@ function searchView(doc, data, query, width) {
|
|
|
127
127
|
matches.append(file);
|
|
128
128
|
}
|
|
129
129
|
if (!groups.size) matches.append(node("p", "fm-empty", "No matching lines."));
|
|
130
|
-
const paths = (
|
|
130
|
+
const paths = (loaded.skipped_files?.skipped_files ?? []).filter((path) => path.toLocaleLowerCase().includes(query));
|
|
131
131
|
for (const path of paths) skipped.append(node("div", "fm-skipped-path", path));
|
|
132
|
-
if (!paths.length) skipped.append(node("p", "fm-empty",
|
|
132
|
+
if (!paths.length) skipped.append(node("p", "fm-empty", data.skipped_files_count === 0
|
|
133
|
+
? "No skipped files." : loaded.skipped_filesState === "error"
|
|
134
|
+
? "Skipped-file resource could not be loaded." : loaded.skipped_filesState === "ready"
|
|
135
|
+
? "No skipped files match the filter." : "Loading skipped files…"));
|
|
136
|
+
if (data.matches_truncated) matches.prepend(node("p", "fm-empty",
|
|
137
|
+
`Showing ${data.matches.length} of ${data.matches_count} matching lines (matches_truncated=true). Narrow the search or increase max_matches.`));
|
|
133
138
|
view.append(tabs, matches, skipped);
|
|
134
139
|
return view;
|
|
135
140
|
}
|
|
@@ -212,10 +217,10 @@ export function workspacePresentation(data, loaded = {}) {
|
|
|
212
217
|
}
|
|
213
218
|
if (Array.isArray(data.matches)) {
|
|
214
219
|
return {
|
|
215
|
-
toolName: "
|
|
220
|
+
toolName: "workspace_text_search", summary: data, records: null,
|
|
216
221
|
resourceFields: resources(data), filterPlaceholder: "Search results",
|
|
217
|
-
count: `${data.matches.length}
|
|
218
|
-
render: (doc, query, width) => searchView(doc, data, query, width),
|
|
222
|
+
count: `${data.matches.length}/${data.matches_count} matching lines · ${data.skipped_files_count} skipped files`,
|
|
223
|
+
render: (doc, query, width) => searchView(doc, data, query, width, loaded),
|
|
219
224
|
};
|
|
220
225
|
}
|
|
221
226
|
const toolName = Array.isArray(data.entries) ? "workspace_list"
|
|
@@ -14,6 +14,7 @@ class CustomBuildHook(BuildHookInterface):
|
|
|
14
14
|
"""Produce frontend assets as part of the wheel build."""
|
|
15
15
|
|
|
16
16
|
def initialize(self, version: str, build_data: dict[str, object]) -> None:
|
|
17
|
+
"""Build missing frontend assets and include them in the wheel artifacts."""
|
|
17
18
|
frontend = Path(self.root) / "frontend"
|
|
18
19
|
outputs = [
|
|
19
20
|
*(
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "forge-cpp-mcp"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "A safe MCP server for structured C++ project development workflows."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -15,6 +15,7 @@ dependencies = [
|
|
|
15
15
|
"mcp[cli]==2.1.1",
|
|
16
16
|
"pydantic>=2.13",
|
|
17
17
|
"regex>=2024.11.6",
|
|
18
|
+
"markdown-it-py>=4.0",
|
|
18
19
|
]
|
|
19
20
|
|
|
20
21
|
[project.optional-dependencies]
|
|
@@ -36,10 +36,12 @@ class Widget:
|
|
|
36
36
|
|
|
37
37
|
@property
|
|
38
38
|
def uri(self) -> str:
|
|
39
|
+
"""Return the stable UI resource URI derived from the packaged filename."""
|
|
39
40
|
return f"ui://forgemcp/{PurePosixPath(self.path).name}"
|
|
40
41
|
|
|
41
42
|
@property
|
|
42
43
|
def content(self) -> str:
|
|
44
|
+
"""Read the packaged widget as UTF-8 HTML."""
|
|
43
45
|
return package_file(self.path).read_text(encoding="utf-8")
|
|
44
46
|
|
|
45
47
|
|
|
@@ -52,6 +54,7 @@ class IconFile:
|
|
|
52
54
|
|
|
53
55
|
@property
|
|
54
56
|
def icon(self) -> Icon:
|
|
57
|
+
"""Encode the packaged icon as portable MCP metadata with its MIME type and sizes."""
|
|
55
58
|
resource = package_file(self.path)
|
|
56
59
|
mime_type = mimetypes.guess_type(self.path)[0] or "application/octet-stream"
|
|
57
60
|
encoded = base64.b64encode(resource.read_bytes()).decode("ascii")
|
|
@@ -21,6 +21,7 @@ class ClangdRequestError(ClangdError):
|
|
|
21
21
|
"""The language server rejected an operation."""
|
|
22
22
|
|
|
23
23
|
def __init__(self, method: str, code: int, message: str) -> None:
|
|
24
|
+
"""Retain the server error code and describe the failed LSP method."""
|
|
24
25
|
super().__init__(f"{method} failed ({code}): {message}")
|
|
25
26
|
self.code = code
|
|
26
27
|
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Language values shared by session decoding and service results."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field, model_validator
|
|
8
|
+
|
|
9
|
+
from forgemcp.workspace.path import WorkspacePath
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Position(BaseModel):
|
|
13
|
+
"""A one-based source line and zero-based Unicode character offset."""
|
|
14
|
+
|
|
15
|
+
line: int = Field(ge=1)
|
|
16
|
+
character: int = Field(ge=0, description="Zero-based Unicode code point offset.")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SourceRange(BaseModel):
|
|
20
|
+
"""An ordered pair of source positions delimiting a range."""
|
|
21
|
+
|
|
22
|
+
start: Position
|
|
23
|
+
end: Position
|
|
24
|
+
|
|
25
|
+
@model_validator(mode="after")
|
|
26
|
+
def ordered(self) -> SourceRange:
|
|
27
|
+
"""Reject ranges whose end precedes the start."""
|
|
28
|
+
if (self.end.line, self.end.character) < (self.start.line, self.start.character):
|
|
29
|
+
raise ValueError("Source range end precedes its start.")
|
|
30
|
+
return self
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Location(BaseModel):
|
|
34
|
+
"""A qualified file path and source range."""
|
|
35
|
+
|
|
36
|
+
path: WorkspacePath
|
|
37
|
+
range: SourceRange
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SourceExcerpt(BaseModel):
|
|
41
|
+
"""Immutable source context captured with a navigation answer."""
|
|
42
|
+
|
|
43
|
+
start_line: int = Field(ge=1)
|
|
44
|
+
text: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class NavigationLocation(Location):
|
|
48
|
+
"""A navigation target with optional saved source context."""
|
|
49
|
+
|
|
50
|
+
preview: SourceExcerpt | None = Field(
|
|
51
|
+
default=None,
|
|
52
|
+
description="Up to seven saved source lines near the target. External or unreadable files have no preview.",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RelatedDiagnostic(BaseModel):
|
|
57
|
+
"""A diagnostic note attached to another source location."""
|
|
58
|
+
|
|
59
|
+
location: Location
|
|
60
|
+
message: str
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Diagnostic(BaseModel):
|
|
64
|
+
"""A source diagnostic with severity, provenance, tags, and related notes."""
|
|
65
|
+
|
|
66
|
+
range: SourceRange
|
|
67
|
+
severity: Literal["error", "warning", "information", "hint"] | None = None
|
|
68
|
+
message: str
|
|
69
|
+
code: int | str | None = None
|
|
70
|
+
source: str | None = None
|
|
71
|
+
tags: list[int] = Field(default_factory=list)
|
|
72
|
+
related: list[RelatedDiagnostic] = Field(default_factory=list)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class HoverText(BaseModel):
|
|
76
|
+
"""One hover fragment with its format and optional code language."""
|
|
77
|
+
|
|
78
|
+
kind: Literal["plaintext", "markdown", "code"]
|
|
79
|
+
text: str
|
|
80
|
+
language: str | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Hover(BaseModel):
|
|
84
|
+
"""Hover fragments and the optional source range they describe."""
|
|
85
|
+
|
|
86
|
+
contents: list[HoverText]
|
|
87
|
+
range: SourceRange | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class DocumentSymbol(BaseModel):
|
|
91
|
+
"""A hierarchical symbol with its full range and name selection range."""
|
|
92
|
+
|
|
93
|
+
name: str
|
|
94
|
+
kind: int
|
|
95
|
+
range: SourceRange
|
|
96
|
+
selection_range: SourceRange
|
|
97
|
+
detail: str | None = None
|
|
98
|
+
tags: list[int] = Field(default_factory=list)
|
|
99
|
+
children: list[DocumentSymbol] = Field(default_factory=list)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class WorkspaceSymbol(BaseModel):
|
|
103
|
+
"""A workspace symbol with a navigation target and optional containing scope."""
|
|
104
|
+
|
|
105
|
+
name: str
|
|
106
|
+
kind: int
|
|
107
|
+
location: NavigationLocation
|
|
108
|
+
container_name: str | None = None
|
|
109
|
+
tags: list[int] = Field(default_factory=list)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class HighlightSpan(BaseModel):
|
|
113
|
+
"""A semantic source range with its token kind and modifiers."""
|
|
114
|
+
|
|
115
|
+
range: SourceRange
|
|
116
|
+
kind: str
|
|
117
|
+
modifiers: list[str] = Field(default_factory=list)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class DiagnosticsResult(BaseModel):
|
|
121
|
+
"""Diagnostics for one file with the configurations that produced them."""
|
|
122
|
+
|
|
123
|
+
configurations: list[str]
|
|
124
|
+
path: WorkspacePath
|
|
125
|
+
diagnostics: list[Diagnostic]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class HoverResult(BaseModel):
|
|
129
|
+
"""A hover answer at a source position with configuration provenance."""
|
|
130
|
+
|
|
131
|
+
configurations: list[str]
|
|
132
|
+
path: WorkspacePath
|
|
133
|
+
position: Position
|
|
134
|
+
hover: Hover | None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class DefinitionResult(BaseModel):
|
|
138
|
+
"""Definition targets shared by the listed configurations."""
|
|
139
|
+
|
|
140
|
+
configurations: list[str]
|
|
141
|
+
locations: list[NavigationLocation]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class ReferencesResult(BaseModel):
|
|
145
|
+
"""Reference targets shared by the listed configurations."""
|
|
146
|
+
|
|
147
|
+
configurations: list[str]
|
|
148
|
+
locations: list[NavigationLocation]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class DocumentSymbolsResult(BaseModel):
|
|
152
|
+
"""A file's symbol tree with configuration provenance."""
|
|
153
|
+
|
|
154
|
+
configurations: list[str]
|
|
155
|
+
path: WorkspacePath
|
|
156
|
+
symbols: list[DocumentSymbol]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class WorkspaceSymbolsResult(BaseModel):
|
|
160
|
+
"""Matching workspace symbols with configuration provenance."""
|
|
161
|
+
|
|
162
|
+
configurations: list[str]
|
|
163
|
+
symbols: list[WorkspaceSymbol]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class HighlightingResult(BaseModel):
|
|
167
|
+
"""Semantic spans for a file with configuration provenance."""
|
|
168
|
+
|
|
169
|
+
configurations: list[str]
|
|
170
|
+
path: WorkspacePath
|
|
171
|
+
spans: list[HighlightSpan]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class FileAnalysis(BaseModel):
|
|
175
|
+
"""Saved diagnostic and highlighting answers for one file."""
|
|
176
|
+
|
|
177
|
+
path: WorkspacePath
|
|
178
|
+
diagnostics: list[DiagnosticsResult]
|
|
179
|
+
highlighting: list[HighlightingResult]
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class ClangdResource(BaseModel):
|
|
183
|
+
"""The versioned payload of an immutable workspace analysis resource."""
|
|
184
|
+
|
|
185
|
+
version: Literal[1] = 1
|
|
186
|
+
files: list[FileAnalysis]
|