forge-cpp-mcp 0.2.4__tar.gz → 0.3.0__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.4 → forge_cpp_mcp-0.3.0}/PKG-INFO +108 -4
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/README.md +103 -3
- forge_cpp_mcp-0.3.0/frontend/admin.html +12 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/package.json +1 -1
- forge_cpp_mcp-0.3.0/frontend/sandbox.html +11 -0
- forge_cpp_mcp-0.3.0/frontend/src/admin-sandbox.js +43 -0
- forge_cpp_mcp-0.3.0/frontend/src/admin-widget.js +133 -0
- forge_cpp_mcp-0.3.0/frontend/src/admin.css +82 -0
- forge_cpp_mcp-0.3.0/frontend/src/admin.js +793 -0
- forge_cpp_mcp-0.3.0/frontend/tests/admin-widget.test.js +170 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/vite.config.js +2 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/hatch_build.py +2 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/pyproject.toml +5 -1
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/__init__.py +1 -1
- forge_cpp_mcp-0.3.0/src/forgemcp/admin/__init__.py +1 -0
- forge_cpp_mcp-0.3.0/src/forgemcp/admin/errors.py +10 -0
- forge_cpp_mcp-0.3.0/src/forgemcp/admin/history.py +157 -0
- forge_cpp_mcp-0.3.0/src/forgemcp/admin/instance.py +458 -0
- forge_cpp_mcp-0.3.0/src/forgemcp/admin/service.py +549 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/clangd/service.py +18 -5
- forge_cpp_mcp-0.3.0/src/forgemcp/cmake/build_tree.py +67 -0
- forge_cpp_mcp-0.3.0/src/forgemcp/cmake/file_api.py +229 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/models.py +17 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/profiles.py +21 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/service.py +218 -200
- forge_cpp_mcp-0.3.0/src/forgemcp/cmake/workspace.py +122 -0
- forge_cpp_mcp-0.3.0/src/forgemcp/local_ipc/__init__.py +23 -0
- forge_cpp_mcp-0.3.0/src/forgemcp/local_ipc/errors.py +21 -0
- forge_cpp_mcp-0.3.0/src/forgemcp/local_ipc/service.py +159 -0
- forge_cpp_mcp-0.3.0/src/forgemcp/local_ipc/unix.py +295 -0
- forge_cpp_mcp-0.3.0/src/forgemcp/local_ipc/windows.py +316 -0
- forge_cpp_mcp-0.3.0/src/forgemcp/process/host.py +95 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/process/service.py +16 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/progress.py +7 -0
- forge_cpp_mcp-0.3.0/src/forgemcp/server.py +486 -0
- forge_cpp_mcp-0.2.4/src/forgemcp/server.py +0 -198
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/.gitignore +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/LICENSE +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/clangd-result.html +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/cmake-build.html +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/cmake-configure.html +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/cmake-profiles.html +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/cmake-run-test.html +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/cmake-test.html +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/package-lock.json +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/process-details.html +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/process-overview.html +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/clangd-navigation-view.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/clangd-result.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/clangd-view.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/cmake-build.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/cmake-configure.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/cmake-profiles.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/cmake-run-test.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/cmake-test.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/cmake-view.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/process-details.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/process-overview.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/process-status.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/process.css +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/shared/app.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/shared/copy-icon.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/shared/presentation.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/shared/result-resources.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/shared/result-view.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/shared/widget.css +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/source-view.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/toolsets.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/workspace-file.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/workspace-result.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/workspace-search.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/workspace-tree.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/src/workspace-view.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/tests/result-resources.test.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/tests/result-view.test.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/tests/workspace.test.js +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/toolsets.html +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/workspace-file.html +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/workspace-result.html +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/workspace-search.html +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/frontend/workspace-tree.html +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/assets.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/clangd/__init__.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/clangd/errors.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/clangd/models.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/clangd/session.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/clangd/text.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/__init__.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/errors.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/text.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/completion.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/clangd.LICENSE +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/clangd.svg +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/cmake.svg +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/process.svg +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/toolchain.svg +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/workspace-edit.svg +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/workspace-file.svg +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/workspace-search.svg +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/workspace.svg +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/markdown.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/process/__init__.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/process/errors.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/process/models.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/process/text.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/process/transcript.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/text.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/__init__.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/discovery.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/errors.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/loader.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/providers/__init__.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/providers/system.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/providers/user.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/providers/visual_studio.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/service.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/spec.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/__init__.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/clang.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/clang_cl.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/clangd.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/clangxx.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/cmake.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/cppvsdbg.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/ctest.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/gcc.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/gdb.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/git.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/gxx.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/link.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/lld.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/lld_link.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/lldb_dap.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/make.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/msbuild.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/msvc.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/ninja.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/__init__.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/diff.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/errors.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/metadata.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/models.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/path.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/providers.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/service.py +0 -0
- {forge_cpp_mcp-0.2.4 → forge_cpp_mcp-0.3.0}/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.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -12,8 +12,12 @@ License-File: LICENSE
|
|
|
12
12
|
Requires-Python: >=3.13
|
|
13
13
|
Requires-Dist: markdown-it-py>=4.0
|
|
14
14
|
Requires-Dist: mcp[cli]==2.1.1
|
|
15
|
+
Requires-Dist: psutil<8,>=7.2
|
|
15
16
|
Requires-Dist: pydantic>=2.13
|
|
17
|
+
Requires-Dist: pywin32>=311; sys_platform == 'win32'
|
|
16
18
|
Requires-Dist: regex>=2024.11.6
|
|
19
|
+
Requires-Dist: starlette>=0.48.0
|
|
20
|
+
Requires-Dist: uvicorn>=0.31.1
|
|
17
21
|
Provides-Extra: build
|
|
18
22
|
Requires-Dist: build>=1.3; extra == 'build'
|
|
19
23
|
Provides-Extra: dev
|
|
@@ -96,19 +100,20 @@ and install it in a virtual environment:
|
|
|
96
100
|
|
|
97
101
|
```powershell
|
|
98
102
|
python -m venv .venv
|
|
99
|
-
.\.venv\Scripts\python.exe -m pip install .\forge_cpp_mcp-0.
|
|
103
|
+
.\.venv\Scripts\python.exe -m pip install .\forge_cpp_mcp-0.3.0-py3-none-any.whl
|
|
100
104
|
.\.venv\Scripts\forgemcp.exe --help
|
|
101
105
|
```
|
|
102
106
|
|
|
103
107
|
The wheel includes the compiled HTML widgets and icons. No npm commands or separate
|
|
104
|
-
frontend deployment are needed.
|
|
108
|
+
frontend deployment are needed. It also packages the local admin panel and its
|
|
109
|
+
isolated widget host. CMake, Ninja, compilers, and clangd are external
|
|
105
110
|
tools: install the ones needed by your workflows separately.
|
|
106
111
|
The distribution name is `forge-cpp-mcp`; the Python module and server command are `forgemcp`.
|
|
107
112
|
|
|
108
113
|
After the package is published to PyPI, the install command can instead be:
|
|
109
114
|
|
|
110
115
|
```powershell
|
|
111
|
-
.\.venv\Scripts\python.exe -m pip install forge-cpp-mcp==0.
|
|
116
|
+
.\.venv\Scripts\python.exe -m pip install forge-cpp-mcp==0.3.0
|
|
112
117
|
```
|
|
113
118
|
|
|
114
119
|
## Setup from source
|
|
@@ -152,6 +157,75 @@ builds the wheel first and then starts the server for this repository.
|
|
|
152
157
|
|
|
153
158
|
All MCP traffic uses stdout. Operational logs must go to stderr.
|
|
154
159
|
|
|
160
|
+
## Local admin panel
|
|
161
|
+
|
|
162
|
+
Normal CLI launches automatically join one admin Hub for the current OS user.
|
|
163
|
+
Each server remains an independent stdio process with its own workspace,
|
|
164
|
+
environment, toolsets, processes, and clangd sessions. The Hub presents them as
|
|
165
|
+
separate browser tabs; it does not move their services into a shared process.
|
|
166
|
+
|
|
167
|
+
Open the panel while at least one server is running:
|
|
168
|
+
|
|
169
|
+
```powershell
|
|
170
|
+
forgemcp --open-admin
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
This command retrieves the private browser URL through same-user local IPC and
|
|
174
|
+
opens the system browser, then exits. The startup message contains the public
|
|
175
|
+
address and this command; the authentication token is never printed. Opening the
|
|
176
|
+
public address in a new browser session alone does not authenticate that session.
|
|
177
|
+
|
|
178
|
+
Each instance tab offers:
|
|
179
|
+
|
|
180
|
+
- tool-call history from both MCP clients and the browser, with original arguments,
|
|
181
|
+
results, progress, timestamps, and errors;
|
|
182
|
+
- the original MCP App widget, text fallback, and complete JSON result;
|
|
183
|
+
- tool schemas, static resources, resource templates, and resource reading;
|
|
184
|
+
- startup settings, process status, discovered toolsets, CMake configurations,
|
|
185
|
+
and retained clangd sessions;
|
|
186
|
+
- browser responses to SDK elicitation requests, including external-file read
|
|
187
|
+
confirmation for commands initiated from the panel.
|
|
188
|
+
|
|
189
|
+
Settings are read-only. Command controls start hidden; select **Enable command
|
|
190
|
+
controls** to call an existing tool with literal JSON arguments; the catalog shows
|
|
191
|
+
its input schema. These
|
|
192
|
+
calls pass through the same SDK handlers, validation, and feature locks as stdio
|
|
193
|
+
calls. There is no panel-wide execution queue. Existing modules decide which
|
|
194
|
+
operations may run concurrently.
|
|
195
|
+
The panel can cancel its own running commands; calls initiated by an MCP client
|
|
196
|
+
remain owned by that client.
|
|
197
|
+
|
|
198
|
+
The panel listens on `127.0.0.1`, preferring port 8765 and choosing a free port when
|
|
199
|
+
that port is unavailable. Widgets use a second loopback port for their isolated
|
|
200
|
+
host. The Hub stops shortly after the last server session ends; browser tabs do
|
|
201
|
+
not keep it running. A temporary IPC disconnect is shown as reconnecting while
|
|
202
|
+
the underlying server process remains alive. Lost tool calls are never replayed
|
|
203
|
+
automatically; read the instance history after reconnecting before repeating one.
|
|
204
|
+
|
|
205
|
+
History and linked widget result resources live only in the server's memory.
|
|
206
|
+
Stopping that server loses its history; there is no database or disk archive.
|
|
207
|
+
Process transcripts retain their existing in-memory lifecycle and are not copied
|
|
208
|
+
into a separate admin log.
|
|
209
|
+
|
|
210
|
+
Use these operator options when needed:
|
|
211
|
+
|
|
212
|
+
```powershell
|
|
213
|
+
forgemcp --no-admin
|
|
214
|
+
forgemcp --admin-port 9000
|
|
215
|
+
forgemcp --admin-ipc forgemcp-other
|
|
216
|
+
forgemcp --open-admin --admin-ipc forgemcp-other
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`--no-admin` opts that instance out of the panel. `--admin-port` is a preference
|
|
220
|
+
when starting a new Hub; it does not change an already running Hub's port.
|
|
221
|
+
Instances using the same `--admin-ipc` name share a Hub; a different name creates
|
|
222
|
+
a separate group for the same OS user.
|
|
223
|
+
|
|
224
|
+
The normal wheel build includes `admin.html` and `sandbox.html` alongside the MCP
|
|
225
|
+
widgets. After changing frontend source, use the documented wheel build or
|
|
226
|
+
`npm run build --prefix frontend`. An unbuilt source checkout returns an explicit
|
|
227
|
+
missing-assets page until those files are generated.
|
|
228
|
+
|
|
155
229
|
## Connect Codex or Claude Code
|
|
156
230
|
|
|
157
231
|
After [installation](#install-a-release) or [source setup](#setup-from-source), copy the appropriate example into the root of the C/C++
|
|
@@ -322,6 +396,36 @@ no source preview because explicit external reads require confirmation.
|
|
|
322
396
|
|
|
323
397
|
## CMake profiles
|
|
324
398
|
|
|
399
|
+
CMake owns which configurations can supply clangd compilation contexts. After a
|
|
400
|
+
successful configure/generate, ForgeMCP reads `cmakeFiles-v1` from a new CMake
|
|
401
|
+
File API reply and records SHA-256 hashes of that build directory's configuration
|
|
402
|
+
inputs in `project/` and `storage/`. Hashes and dependency lists live in memory;
|
|
403
|
+
restart the server and run `cmake_configure` to confirm contexts again. Existing
|
|
404
|
+
File API replies and `compile_commands.json` alone do not confirm current inputs.
|
|
405
|
+
|
|
406
|
+
Workspace writes, edits, moves, deletions, and directory creation check known
|
|
407
|
+
inputs at the operation boundary. Actual input changes withdraw affected contexts
|
|
408
|
+
and notify clangd through the CMake subscription; identical content and ordinary
|
|
409
|
+
source/header edits keep contexts available unless those files are CMake inputs.
|
|
410
|
+
Known preset files are treated conservatively without resolving preset inheritance
|
|
411
|
+
or includes. CONFIGURE_DEPENDS globs conservatively invalidate on structural
|
|
412
|
+
changes; older File API replies without glob information use this policy for all
|
|
413
|
+
configurations. No project-wide dependency scan or CMake/glob parser is used.
|
|
414
|
+
|
|
415
|
+
Run `cmake_configure` to restore outdated or unknown contexts. `cmake_build` is
|
|
416
|
+
also allowed for outdated configurations: a new successful File API generation
|
|
417
|
+
can restore them even when later compilation fails. A successful build without
|
|
418
|
+
regeneration does not refresh the input baseline. Failed configure/generate cannot
|
|
419
|
+
reactivate old artifacts, and invalidation leaves build trees and databases intact.
|
|
420
|
+
Unchanged contexts and database contents retain their clangd sessions.
|
|
421
|
+
|
|
422
|
+
Checks occur only during Workspace mutations, configure, and build. There are no
|
|
423
|
+
filesystem watchers, polling, or background scans, and no guarantee of detecting
|
|
424
|
+
external editor/process changes. External `root/` dependencies are omitted from
|
|
425
|
+
hash tracking without elicitation or reads; changes to those inputs require an
|
|
426
|
+
explicit configure. Native preset contexts still require a known managed build
|
|
427
|
+
directory and a compilation database.
|
|
428
|
+
|
|
325
429
|
`cmake_profiles` lists profiles. `cmake_configure`, `cmake_build`, and `cmake_test`
|
|
326
430
|
operate on all profiles unless a nonempty `profiles` list selects a subset.
|
|
327
431
|
Configuration must precede building, and building must precede testing. Profiles
|
|
@@ -71,19 +71,20 @@ and install it in a virtual environment:
|
|
|
71
71
|
|
|
72
72
|
```powershell
|
|
73
73
|
python -m venv .venv
|
|
74
|
-
.\.venv\Scripts\python.exe -m pip install .\forge_cpp_mcp-0.
|
|
74
|
+
.\.venv\Scripts\python.exe -m pip install .\forge_cpp_mcp-0.3.0-py3-none-any.whl
|
|
75
75
|
.\.venv\Scripts\forgemcp.exe --help
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
The wheel includes the compiled HTML widgets and icons. No npm commands or separate
|
|
79
|
-
frontend deployment are needed.
|
|
79
|
+
frontend deployment are needed. It also packages the local admin panel and its
|
|
80
|
+
isolated widget host. CMake, Ninja, compilers, and clangd are external
|
|
80
81
|
tools: install the ones needed by your workflows separately.
|
|
81
82
|
The distribution name is `forge-cpp-mcp`; the Python module and server command are `forgemcp`.
|
|
82
83
|
|
|
83
84
|
After the package is published to PyPI, the install command can instead be:
|
|
84
85
|
|
|
85
86
|
```powershell
|
|
86
|
-
.\.venv\Scripts\python.exe -m pip install forge-cpp-mcp==0.
|
|
87
|
+
.\.venv\Scripts\python.exe -m pip install forge-cpp-mcp==0.3.0
|
|
87
88
|
```
|
|
88
89
|
|
|
89
90
|
## Setup from source
|
|
@@ -127,6 +128,75 @@ builds the wheel first and then starts the server for this repository.
|
|
|
127
128
|
|
|
128
129
|
All MCP traffic uses stdout. Operational logs must go to stderr.
|
|
129
130
|
|
|
131
|
+
## Local admin panel
|
|
132
|
+
|
|
133
|
+
Normal CLI launches automatically join one admin Hub for the current OS user.
|
|
134
|
+
Each server remains an independent stdio process with its own workspace,
|
|
135
|
+
environment, toolsets, processes, and clangd sessions. The Hub presents them as
|
|
136
|
+
separate browser tabs; it does not move their services into a shared process.
|
|
137
|
+
|
|
138
|
+
Open the panel while at least one server is running:
|
|
139
|
+
|
|
140
|
+
```powershell
|
|
141
|
+
forgemcp --open-admin
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
This command retrieves the private browser URL through same-user local IPC and
|
|
145
|
+
opens the system browser, then exits. The startup message contains the public
|
|
146
|
+
address and this command; the authentication token is never printed. Opening the
|
|
147
|
+
public address in a new browser session alone does not authenticate that session.
|
|
148
|
+
|
|
149
|
+
Each instance tab offers:
|
|
150
|
+
|
|
151
|
+
- tool-call history from both MCP clients and the browser, with original arguments,
|
|
152
|
+
results, progress, timestamps, and errors;
|
|
153
|
+
- the original MCP App widget, text fallback, and complete JSON result;
|
|
154
|
+
- tool schemas, static resources, resource templates, and resource reading;
|
|
155
|
+
- startup settings, process status, discovered toolsets, CMake configurations,
|
|
156
|
+
and retained clangd sessions;
|
|
157
|
+
- browser responses to SDK elicitation requests, including external-file read
|
|
158
|
+
confirmation for commands initiated from the panel.
|
|
159
|
+
|
|
160
|
+
Settings are read-only. Command controls start hidden; select **Enable command
|
|
161
|
+
controls** to call an existing tool with literal JSON arguments; the catalog shows
|
|
162
|
+
its input schema. These
|
|
163
|
+
calls pass through the same SDK handlers, validation, and feature locks as stdio
|
|
164
|
+
calls. There is no panel-wide execution queue. Existing modules decide which
|
|
165
|
+
operations may run concurrently.
|
|
166
|
+
The panel can cancel its own running commands; calls initiated by an MCP client
|
|
167
|
+
remain owned by that client.
|
|
168
|
+
|
|
169
|
+
The panel listens on `127.0.0.1`, preferring port 8765 and choosing a free port when
|
|
170
|
+
that port is unavailable. Widgets use a second loopback port for their isolated
|
|
171
|
+
host. The Hub stops shortly after the last server session ends; browser tabs do
|
|
172
|
+
not keep it running. A temporary IPC disconnect is shown as reconnecting while
|
|
173
|
+
the underlying server process remains alive. Lost tool calls are never replayed
|
|
174
|
+
automatically; read the instance history after reconnecting before repeating one.
|
|
175
|
+
|
|
176
|
+
History and linked widget result resources live only in the server's memory.
|
|
177
|
+
Stopping that server loses its history; there is no database or disk archive.
|
|
178
|
+
Process transcripts retain their existing in-memory lifecycle and are not copied
|
|
179
|
+
into a separate admin log.
|
|
180
|
+
|
|
181
|
+
Use these operator options when needed:
|
|
182
|
+
|
|
183
|
+
```powershell
|
|
184
|
+
forgemcp --no-admin
|
|
185
|
+
forgemcp --admin-port 9000
|
|
186
|
+
forgemcp --admin-ipc forgemcp-other
|
|
187
|
+
forgemcp --open-admin --admin-ipc forgemcp-other
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`--no-admin` opts that instance out of the panel. `--admin-port` is a preference
|
|
191
|
+
when starting a new Hub; it does not change an already running Hub's port.
|
|
192
|
+
Instances using the same `--admin-ipc` name share a Hub; a different name creates
|
|
193
|
+
a separate group for the same OS user.
|
|
194
|
+
|
|
195
|
+
The normal wheel build includes `admin.html` and `sandbox.html` alongside the MCP
|
|
196
|
+
widgets. After changing frontend source, use the documented wheel build or
|
|
197
|
+
`npm run build --prefix frontend`. An unbuilt source checkout returns an explicit
|
|
198
|
+
missing-assets page until those files are generated.
|
|
199
|
+
|
|
130
200
|
## Connect Codex or Claude Code
|
|
131
201
|
|
|
132
202
|
After [installation](#install-a-release) or [source setup](#setup-from-source), copy the appropriate example into the root of the C/C++
|
|
@@ -297,6 +367,36 @@ no source preview because explicit external reads require confirmation.
|
|
|
297
367
|
|
|
298
368
|
## CMake profiles
|
|
299
369
|
|
|
370
|
+
CMake owns which configurations can supply clangd compilation contexts. After a
|
|
371
|
+
successful configure/generate, ForgeMCP reads `cmakeFiles-v1` from a new CMake
|
|
372
|
+
File API reply and records SHA-256 hashes of that build directory's configuration
|
|
373
|
+
inputs in `project/` and `storage/`. Hashes and dependency lists live in memory;
|
|
374
|
+
restart the server and run `cmake_configure` to confirm contexts again. Existing
|
|
375
|
+
File API replies and `compile_commands.json` alone do not confirm current inputs.
|
|
376
|
+
|
|
377
|
+
Workspace writes, edits, moves, deletions, and directory creation check known
|
|
378
|
+
inputs at the operation boundary. Actual input changes withdraw affected contexts
|
|
379
|
+
and notify clangd through the CMake subscription; identical content and ordinary
|
|
380
|
+
source/header edits keep contexts available unless those files are CMake inputs.
|
|
381
|
+
Known preset files are treated conservatively without resolving preset inheritance
|
|
382
|
+
or includes. CONFIGURE_DEPENDS globs conservatively invalidate on structural
|
|
383
|
+
changes; older File API replies without glob information use this policy for all
|
|
384
|
+
configurations. No project-wide dependency scan or CMake/glob parser is used.
|
|
385
|
+
|
|
386
|
+
Run `cmake_configure` to restore outdated or unknown contexts. `cmake_build` is
|
|
387
|
+
also allowed for outdated configurations: a new successful File API generation
|
|
388
|
+
can restore them even when later compilation fails. A successful build without
|
|
389
|
+
regeneration does not refresh the input baseline. Failed configure/generate cannot
|
|
390
|
+
reactivate old artifacts, and invalidation leaves build trees and databases intact.
|
|
391
|
+
Unchanged contexts and database contents retain their clangd sessions.
|
|
392
|
+
|
|
393
|
+
Checks occur only during Workspace mutations, configure, and build. There are no
|
|
394
|
+
filesystem watchers, polling, or background scans, and no guarantee of detecting
|
|
395
|
+
external editor/process changes. External `root/` dependencies are omitted from
|
|
396
|
+
hash tracking without elicitation or reads; changes to those inputs require an
|
|
397
|
+
explicit configure. Native preset contexts still require a known managed build
|
|
398
|
+
directory and a compilation database.
|
|
399
|
+
|
|
300
400
|
`cmake_profiles` lists profiles. `cmake_configure`, `cmake_build`, and `cmake_test`
|
|
301
401
|
operate on all profiles unless a nonempty `profiles` list selects a subset.
|
|
302
402
|
Configuration must precede building, and building must precede testing. Profiles
|
|
@@ -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>ForgeMCP admin</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<main id="admin" class="fm-widget fm-admin" aria-label="ForgeMCP administration"></main>
|
|
10
|
+
<script type="module" src="/src/admin.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 cmake-run-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 && vite build --mode admin && vite build --mode sandbox",
|
|
7
7
|
"test": "node --test tests/*.test.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
@@ -0,0 +1,11 @@
|
|
|
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>ForgeMCP widget sandbox</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<script type="module" src="/src/admin-sandbox.js"></script>
|
|
10
|
+
</body>
|
|
11
|
+
</html>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Relay one packaged App through a separate-origin, network-restricted sandbox. */
|
|
2
|
+
const parentOrigin = new URLSearchParams(location.hash.slice(1)).get("parent_origin");
|
|
3
|
+
let parentUrl;
|
|
4
|
+
try { parentUrl = new URL(parentOrigin); } catch { /* An invalid parent never receives messages. */ }
|
|
5
|
+
const loopback = (url) => url?.protocol === "http:"
|
|
6
|
+
&& ["127.0.0.1", "localhost", "[::1]"].includes(url.hostname);
|
|
7
|
+
|
|
8
|
+
if (window.parent !== window && loopback(parentUrl) && parentUrl.origin !== location.origin) {
|
|
9
|
+
let view = null;
|
|
10
|
+
const internal = (message) => typeof message?.method === "string"
|
|
11
|
+
&& message.method.startsWith("ui/notifications/sandbox-");
|
|
12
|
+
window.addEventListener("message", (event) => {
|
|
13
|
+
const message = event.data;
|
|
14
|
+
if (!message || message.jsonrpc !== "2.0") return;
|
|
15
|
+
if (event.source === window.parent && event.origin === parentUrl.origin) {
|
|
16
|
+
if (message.method === "ui/notifications/sandbox-resource-ready") {
|
|
17
|
+
if (view || typeof message.params?.html !== "string") return;
|
|
18
|
+
const parsed = new DOMParser().parseFromString(message.params.html, "text/html");
|
|
19
|
+
const csp = parsed.createElement("meta");
|
|
20
|
+
csp.httpEquiv = "Content-Security-Policy";
|
|
21
|
+
csp.content = "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; "
|
|
22
|
+
+ "img-src data: blob:; font-src data:; connect-src 'none'; frame-src 'none'; "
|
|
23
|
+
+ "object-src 'none'; base-uri 'none'; form-action 'none'; navigate-to 'none'";
|
|
24
|
+
parsed.head.prepend(csp);
|
|
25
|
+
view = document.createElement("iframe");
|
|
26
|
+
view.title = "Tool result";
|
|
27
|
+
view.setAttribute("sandbox", "allow-scripts");
|
|
28
|
+
view.style.cssText = "display:block;width:100%;height:100%;border:0";
|
|
29
|
+
view.srcdoc = `<!doctype html>${parsed.documentElement.outerHTML}`;
|
|
30
|
+
document.body.replaceChildren(view);
|
|
31
|
+
} else if (!internal(message) && view) {
|
|
32
|
+
view.contentWindow.postMessage(message, "*");
|
|
33
|
+
}
|
|
34
|
+
} else if (view && event.source === view.contentWindow && !internal(message)) {
|
|
35
|
+
window.parent.postMessage(message, parentUrl.origin);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
document.documentElement.style.cssText = "height:100%;margin:0;background:transparent";
|
|
39
|
+
document.body.style.cssText = "height:100%;margin:0;background:transparent";
|
|
40
|
+
window.parent.postMessage({
|
|
41
|
+
jsonrpc: "2.0", method: "ui/notifications/sandbox-proxy-ready", params: {},
|
|
42
|
+
}, parentUrl.origin);
|
|
43
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { AppBridge, PostMessageTransport, getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge";
|
|
2
|
+
|
|
3
|
+
/** Identify only immutable result resources linked by the original invocation. */
|
|
4
|
+
function linkedUris(result) {
|
|
5
|
+
const uris = new Set();
|
|
6
|
+
const resources = result?.structuredContent?.resources;
|
|
7
|
+
if (!resources || typeof resources !== "object") return uris;
|
|
8
|
+
for (const resource of Object.values(resources)) {
|
|
9
|
+
if (typeof resource?.uri === "string"
|
|
10
|
+
&& /^forgemcp:\/\/workspace\/results\/[0-9a-f]{32}\/[a-zA-Z0-9_-]+\.(json|md)$/.test(resource.uri)) {
|
|
11
|
+
uris.add(resource.uri);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return uris;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Mount one original tool invocation; the frame receives no execution capability. */
|
|
18
|
+
export async function mountInvocationWidget({ container, tool, call, sandboxOrigin, readResource, theme, signal }) {
|
|
19
|
+
const uri = getToolUiResourceUri(tool);
|
|
20
|
+
if (!uri) throw new Error("This tool has no widget resource.");
|
|
21
|
+
const sandbox = new URL(sandboxOrigin);
|
|
22
|
+
if (sandbox.origin === location.origin || sandbox.protocol !== "http:"
|
|
23
|
+
|| !["127.0.0.1", "localhost", "[::1]"].includes(sandbox.hostname)) {
|
|
24
|
+
throw new Error("The widget sandbox requires a separate local origin.");
|
|
25
|
+
}
|
|
26
|
+
const resource = await readResource(uri);
|
|
27
|
+
if (signal?.aborted) throw new DOMException("Widget loading cancelled.", "AbortError");
|
|
28
|
+
const html = resource?.contents?.find((item) => item.uri === uri && typeof item.text === "string");
|
|
29
|
+
if (!html || !String(html.mimeType ?? "").startsWith("text/html")) {
|
|
30
|
+
throw new Error("The widget HTML resource is unavailable.");
|
|
31
|
+
}
|
|
32
|
+
const allowed = linkedUris(call.result);
|
|
33
|
+
const frame = document.createElement("iframe");
|
|
34
|
+
frame.title = `${call.tool_name} result widget`;
|
|
35
|
+
frame.className = "admin-widget-frame";
|
|
36
|
+
frame.setAttribute("sandbox", "allow-scripts allow-same-origin");
|
|
37
|
+
let disposed = false;
|
|
38
|
+
let initialized = false;
|
|
39
|
+
let timeout;
|
|
40
|
+
let resize;
|
|
41
|
+
const context = () => ({
|
|
42
|
+
toolInfo: { id: call.id, tool },
|
|
43
|
+
theme: theme(),
|
|
44
|
+
displayMode: "inline",
|
|
45
|
+
availableDisplayModes: ["inline"],
|
|
46
|
+
locale: navigator.language,
|
|
47
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
48
|
+
platform: "web",
|
|
49
|
+
containerDimensions: { width: Math.max(1, container.clientWidth), maxHeight: 420 },
|
|
50
|
+
safeAreaInsets: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
51
|
+
styles: { variables: { "--font-mono": '"Cascadia Code", Consolas, monospace' } },
|
|
52
|
+
});
|
|
53
|
+
const bridge = new AppBridge(
|
|
54
|
+
null,
|
|
55
|
+
{ name: "ForgeMCP admin", version: "1.0.0" },
|
|
56
|
+
{ serverResources: {} },
|
|
57
|
+
{ hostContext: context() },
|
|
58
|
+
);
|
|
59
|
+
bridge.onreadresource = async ({ uri: requested }) => {
|
|
60
|
+
if (disposed || !allowed.has(requested)) throw new Error("Only resources linked by this invocation are available.");
|
|
61
|
+
return readResource(requested);
|
|
62
|
+
};
|
|
63
|
+
bridge.oncalltool = async () => { throw new Error("Result widgets cannot execute tools."); };
|
|
64
|
+
bridge.onlistresources = async () => ({ resources: [] });
|
|
65
|
+
bridge.onlistresourcetemplates = async () => ({ resourceTemplates: [] });
|
|
66
|
+
bridge.onlistprompts = async () => ({ prompts: [] });
|
|
67
|
+
bridge.onopenlink = async () => { throw new Error("External links are unavailable in result widgets."); };
|
|
68
|
+
bridge.onsizechange = ({ height }) => {
|
|
69
|
+
if (!disposed && Number.isFinite(height) && height > 0) frame.style.height = `${Math.min(420, height)}px`;
|
|
70
|
+
};
|
|
71
|
+
let resolveReady;
|
|
72
|
+
let rejectReady;
|
|
73
|
+
const ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
|
|
74
|
+
ready.catch(() => {});
|
|
75
|
+
bridge.onsandboxready = async () => {
|
|
76
|
+
try {
|
|
77
|
+
if (!disposed) await bridge.sendSandboxResourceReady({
|
|
78
|
+
html: html.text,
|
|
79
|
+
sandbox: "allow-scripts",
|
|
80
|
+
csp: { connectDomains: [], resourceDomains: [], frameDomains: [] },
|
|
81
|
+
});
|
|
82
|
+
} catch (error) { rejectReady(error); }
|
|
83
|
+
};
|
|
84
|
+
bridge.oninitialized = async () => {
|
|
85
|
+
if (disposed) return;
|
|
86
|
+
try {
|
|
87
|
+
initialized = true;
|
|
88
|
+
await bridge.sendToolInput({ arguments: call.arguments ?? {} });
|
|
89
|
+
if (call.result) await bridge.sendToolResult(call.result);
|
|
90
|
+
else if (["cancelled", "interrupted"].includes(call.status)) {
|
|
91
|
+
await bridge.sendToolCancelled({ reason: call.error ?? call.status });
|
|
92
|
+
}
|
|
93
|
+
clearTimeout(timeout);
|
|
94
|
+
resolveReady();
|
|
95
|
+
} catch (error) { rejectReady(error); }
|
|
96
|
+
};
|
|
97
|
+
const dispose = async () => {
|
|
98
|
+
if (disposed) return;
|
|
99
|
+
disposed = true;
|
|
100
|
+
clearTimeout(timeout);
|
|
101
|
+
resize?.disconnect();
|
|
102
|
+
if (initialized) {
|
|
103
|
+
try { await bridge.teardownResource({}, { timeout: 1000 }); } catch { /* A closed frame may no longer answer. */ }
|
|
104
|
+
}
|
|
105
|
+
try { await bridge.close(); } catch { /* Closing a disconnected bridge is harmless. */ }
|
|
106
|
+
frame.remove();
|
|
107
|
+
};
|
|
108
|
+
const aborted = () => {
|
|
109
|
+
rejectReady(new DOMException("Widget loading cancelled.", "AbortError"));
|
|
110
|
+
void dispose();
|
|
111
|
+
};
|
|
112
|
+
signal?.addEventListener("abort", aborted, { once: true });
|
|
113
|
+
container.append(frame);
|
|
114
|
+
try {
|
|
115
|
+
await bridge.connect(new PostMessageTransport(frame.contentWindow, frame.contentWindow));
|
|
116
|
+
if (disposed || signal?.aborted) throw new DOMException("Widget loading cancelled.", "AbortError");
|
|
117
|
+
timeout = setTimeout(() => rejectReady(new Error("The widget did not complete its connection handshake.")), 12000);
|
|
118
|
+
const url = new URL("/sandbox.html", sandbox);
|
|
119
|
+
url.hash = new URLSearchParams({ parent_origin: location.origin }).toString();
|
|
120
|
+
frame.src = url.href;
|
|
121
|
+
await ready;
|
|
122
|
+
resize = new ResizeObserver(() => { if (!disposed) bridge.setHostContext(context()); });
|
|
123
|
+
resize.observe(container);
|
|
124
|
+
return {
|
|
125
|
+
dispose: async () => { signal?.removeEventListener("abort", aborted); await dispose(); },
|
|
126
|
+
refreshContext: () => { if (!disposed) bridge.setHostContext(context()); },
|
|
127
|
+
};
|
|
128
|
+
} catch (error) {
|
|
129
|
+
signal?.removeEventListener("abort", aborted);
|
|
130
|
+
await dispose();
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/* The admin shell uses the same palette, fonts and compact controls as result widgets. */
|
|
2
|
+
.fm-admin { height: 100dvh; min-height: 380px; display: flex; flex-direction: column; background: var(--fm-bg); }
|
|
3
|
+
.fm-admin button, .fm-admin input, .fm-admin select, .fm-admin textarea { font: inherit; color: inherit; border: 1px solid var(--fm-border); background: var(--fm-bar); border-radius: 0; }
|
|
4
|
+
.fm-admin button { padding: 5px 9px; cursor: pointer; }
|
|
5
|
+
.fm-admin button:disabled { cursor: default; opacity: .55; }
|
|
6
|
+
.fm-admin :is(button, input, select, textarea):focus-visible { outline: 2px solid var(--fm-accent); outline-offset: 2px; }
|
|
7
|
+
.fm-admin button[aria-selected="true"], .fm-admin button[aria-pressed="true"] { color: var(--fm-accent); border-color: var(--fm-accent); }
|
|
8
|
+
.fm-admin h1, .fm-admin h2, .fm-admin h3 { margin: 0; font: inherit; font-weight: 600; overflow-wrap: anywhere; }
|
|
9
|
+
.admin-header { display: flex; align-items: center; gap: 12px; padding: 9px 14px; background: var(--fm-bar); border-bottom: 1px solid var(--fm-border); flex-wrap: wrap; }
|
|
10
|
+
.admin-header h1 { color: var(--fm-warning); margin-right: auto; }
|
|
11
|
+
.admin-header label { display: flex; align-items: center; gap: 6px; }
|
|
12
|
+
.admin-connection { color: var(--fm-muted); }
|
|
13
|
+
.admin-connection[data-live="true"] { color: var(--fm-success); }
|
|
14
|
+
.admin-instances { display: flex; gap: 5px; padding: 7px 10px; flex: none; border-bottom: 1px solid var(--fm-border); overflow-x: auto; }
|
|
15
|
+
.admin-instance-tab { display: flex; align-items: center; gap: 8px; max-width: 48ch; flex: none; text-align: left; white-space: normal; overflow-wrap: anywhere; }
|
|
16
|
+
.admin-dot { height: 7px; width: 7px; min-width: 7px; background: var(--fm-muted); }
|
|
17
|
+
.admin-dot[data-connected="true"] { background: var(--fm-success); }
|
|
18
|
+
.admin-statusbar { padding: 7px 14px; display: flex; gap: 6px 20px; flex-wrap: wrap; color: var(--fm-muted); border-bottom: 1px solid var(--fm-border); }
|
|
19
|
+
.admin-statusbar span { overflow-wrap: anywhere; }
|
|
20
|
+
.admin-pages { display: flex; gap: 5px; padding: 7px 10px; border-bottom: 1px solid var(--fm-border); flex-wrap: wrap; }
|
|
21
|
+
.admin-pages .admin-refresh { margin-left: auto; }
|
|
22
|
+
.admin-body { flex: 1; min-height: 0; overflow: auto; }
|
|
23
|
+
.admin-elicitations { flex: none; max-height: 50dvh; overflow: auto; border-bottom: 1px solid var(--fm-border); }
|
|
24
|
+
.admin-elicitations:empty { display: none; }
|
|
25
|
+
.admin-confirmation-link { color: var(--fm-accent); overflow-wrap: anywhere; }
|
|
26
|
+
.admin-notice { margin: 0; padding: 8px 14px; color: var(--fm-warning); background: var(--fm-bar); border-bottom: 1px solid var(--fm-border); overflow-wrap: anywhere; }
|
|
27
|
+
.admin-notice[hidden] { display: none; }
|
|
28
|
+
.admin-empty { padding: 28px 18px; color: var(--fm-muted); }
|
|
29
|
+
.admin-history { display: grid; grid-template-columns: minmax(230px, 29%) minmax(0, 1fr); height: 100%; }
|
|
30
|
+
.admin-history-sidebar { display: flex; flex-direction: column; min-height: 0; border-right: 1px solid var(--fm-border); }
|
|
31
|
+
.admin-history-filter { display: flex; gap: 7px; flex-wrap: wrap; padding: 10px; border-bottom: 1px solid var(--fm-border); }
|
|
32
|
+
.admin-history-filter input { width: 100%; padding: 6px 8px; }
|
|
33
|
+
.admin-history-count { color: var(--fm-muted); }
|
|
34
|
+
.admin-history-list { overflow-y: auto; flex: 1; }
|
|
35
|
+
.admin-call { width: 100%; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 3px 8px; text-align: left; border-width: 0 0 1px !important; padding: 9px 10px !important; background: transparent !important; overflow-wrap: anywhere; }
|
|
36
|
+
.admin-call[aria-selected="true"] { background: var(--fm-hover) !important; box-shadow: inset 3px 0 var(--fm-accent); }
|
|
37
|
+
.admin-call-name { color: var(--fm-text); grid-column: 1 / -1; }
|
|
38
|
+
.admin-call-time { color: var(--fm-muted); font-size: 11px; }
|
|
39
|
+
.admin-state { color: var(--fm-muted); overflow-wrap: anywhere; }
|
|
40
|
+
.admin-state[data-state="running"], .admin-state[data-state="queued"] { color: var(--fm-warning); }
|
|
41
|
+
.admin-state[data-state="failed"], .admin-state[data-state="error"], .admin-state[data-state="interrupted"] { color: var(--fm-error); }
|
|
42
|
+
.admin-state[data-state="cancelled"] { color: var(--fm-warning); }
|
|
43
|
+
.admin-state[data-state="completed"], .admin-state[data-state="succeeded"] { color: var(--fm-success); }
|
|
44
|
+
.admin-detail { min-height: 0; overflow: auto; padding: 14px; }
|
|
45
|
+
.admin-detail-heading { display: flex; gap: 8px 16px; align-items: baseline; flex-wrap: wrap; margin-bottom: 10px; }
|
|
46
|
+
.admin-detail-heading h2 { color: var(--fm-accent); }
|
|
47
|
+
.admin-detail-meta { display: grid; grid-template-columns: 14ch minmax(0, 1fr); gap: 4px 10px; margin: 10px 0; }
|
|
48
|
+
.admin-detail-meta dt { color: var(--fm-muted); overflow-wrap: anywhere; }
|
|
49
|
+
.admin-detail-meta dd { margin: 0; overflow-wrap: anywhere; }
|
|
50
|
+
.admin-detail details { margin: 10px 0; }
|
|
51
|
+
.admin-detail summary, .admin-card summary { cursor: pointer; color: var(--fm-muted); }
|
|
52
|
+
.admin-result-tabs { display: flex; gap: 5px; padding: 7px 0; }
|
|
53
|
+
.admin-result { min-height: 70px; }
|
|
54
|
+
.admin-widget-frame { display: block; width: 100%; height: 420px; border: 0; }
|
|
55
|
+
.admin-pre { margin: 7px 0; padding: 10px; border: 1px solid var(--fm-border); background: var(--fm-bar); white-space: pre-wrap; overflow-wrap: anywhere; tab-size: 4; user-select: text; }
|
|
56
|
+
.admin-error { color: var(--fm-error); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
57
|
+
.admin-section { padding: 14px; }
|
|
58
|
+
.admin-section > h2 { margin-bottom: 12px; color: var(--fm-accent); }
|
|
59
|
+
.admin-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 310px), 1fr)); gap: 12px; }
|
|
60
|
+
.admin-card { border: 1px solid var(--fm-border); padding: 12px; min-width: 0; }
|
|
61
|
+
.admin-card h3 { margin-bottom: 8px; }
|
|
62
|
+
.admin-card p { white-space: pre-wrap; overflow-wrap: anywhere; margin: 7px 0; }
|
|
63
|
+
.admin-card .admin-pre { max-height: 500px; overflow: auto; }
|
|
64
|
+
.admin-table-scroll { overflow: auto; margin: 10px 0; }
|
|
65
|
+
.admin-table { width: 100%; border-collapse: collapse; text-align: left; }
|
|
66
|
+
.admin-table :is(th, td) { padding: 7px 9px; vertical-align: top; border: 1px solid var(--fm-border); overflow-wrap: anywhere; }
|
|
67
|
+
.admin-table th { color: var(--fm-muted); background: var(--fm-bar); font-weight: 400; }
|
|
68
|
+
.admin-catalog-group { margin-top: 18px; }
|
|
69
|
+
.admin-command-form { display: grid; gap: 9px; margin-bottom: 15px; border: 1px solid var(--fm-accent); padding: 12px; }
|
|
70
|
+
.admin-command-form label { display: grid; gap: 5px; }
|
|
71
|
+
.admin-command-form textarea { min-height: 140px; width: 100%; padding: 8px; resize: vertical; }
|
|
72
|
+
.admin-command-form select { width: 100%; padding: 6px; }
|
|
73
|
+
.admin-command-form button { justify-self: start; }
|
|
74
|
+
.admin-muted { color: var(--fm-muted); }
|
|
75
|
+
.admin-resource-title { overflow-wrap: anywhere; }
|
|
76
|
+
@media (max-width: 760px) {
|
|
77
|
+
.admin-history { grid-template-columns: 1fr; height: auto; min-height: 100%; }
|
|
78
|
+
.admin-history-sidebar { max-height: 40dvh; min-height: 160px; border-right: 0; border-bottom: 1px solid var(--fm-border); }
|
|
79
|
+
.admin-detail { overflow: visible; }
|
|
80
|
+
.admin-header { gap: 8px; }
|
|
81
|
+
.admin-header label { font-size: 11px; }
|
|
82
|
+
}
|