forge-cpp-mcp 0.2.5__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.5/README.md → forge_cpp_mcp-0.3.0/PKG-INFO +102 -3
- forge_cpp_mcp-0.2.5/PKG-INFO → forge_cpp_mcp-0.3.0/README.md +73 -28
- forge_cpp_mcp-0.3.0/frontend/admin.html +12 -0
- {forge_cpp_mcp-0.2.5 → 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.5 → forge_cpp_mcp-0.3.0}/frontend/vite.config.js +2 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/hatch_build.py +2 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/pyproject.toml +5 -1
- {forge_cpp_mcp-0.2.5 → 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.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.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/process/service.py +16 -0
- {forge_cpp_mcp-0.2.5 → 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.5/src/forgemcp/server.py +0 -200
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/.gitignore +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/LICENSE +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/clangd-result.html +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/cmake-build.html +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/cmake-configure.html +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/cmake-profiles.html +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/cmake-run-test.html +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/cmake-test.html +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/package-lock.json +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/process-details.html +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/process-overview.html +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/clangd-navigation-view.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/clangd-result.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/clangd-view.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/cmake-build.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/cmake-configure.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/cmake-profiles.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/cmake-run-test.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/cmake-test.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/cmake-view.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/process-details.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/process-overview.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/process-status.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/process.css +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/shared/app.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/shared/copy-icon.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/shared/presentation.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/shared/result-resources.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/shared/result-view.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/shared/widget.css +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/source-view.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/toolsets.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/workspace-file.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/workspace-result.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/workspace-search.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/workspace-tree.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/src/workspace-view.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/tests/result-resources.test.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/tests/result-view.test.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/tests/workspace.test.js +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/toolsets.html +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/workspace-file.html +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/workspace-result.html +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/workspace-search.html +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/frontend/workspace-tree.html +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/assets.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/clangd/__init__.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/clangd/errors.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/clangd/models.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/clangd/service.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/clangd/session.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/clangd/text.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/__init__.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/build_tree.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/errors.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/file_api.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/models.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/profiles.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/service.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/text.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/cmake/workspace.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/completion.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/clangd.LICENSE +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/clangd.svg +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/cmake.svg +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/process.svg +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/toolchain.svg +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/workspace-edit.svg +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/workspace-file.svg +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/workspace-search.svg +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/icons/workspace.svg +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/markdown.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/process/__init__.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/process/errors.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/process/models.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/process/text.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/process/transcript.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/text.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/__init__.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/discovery.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/errors.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/loader.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/providers/__init__.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/providers/system.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/providers/user.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/providers/visual_studio.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/service.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/spec.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/__init__.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/clang.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/clang_cl.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/clangd.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/clangxx.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/cmake.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/cppvsdbg.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/ctest.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/gcc.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/gdb.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/git.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/gxx.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/link.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/lld.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/lld_link.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/lldb_dap.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/make.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/msbuild.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/msvc.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/toolchain/tools/ninja.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/__init__.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/diff.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/errors.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/metadata.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/models.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/path.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/providers.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/service.py +0 -0
- {forge_cpp_mcp-0.2.5 → forge_cpp_mcp-0.3.0}/src/forgemcp/workspace/text.py +0 -0
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: forge-cpp-mcp
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A safe MCP server for structured C++ project development workflows.
|
|
5
|
+
Project-URL: Homepage, https://github.com/hardened-steel/ForgeMCP
|
|
6
|
+
Project-URL: Repository, https://github.com/hardened-steel/ForgeMCP
|
|
7
|
+
Project-URL: Issues, https://github.com/hardened-steel/ForgeMCP/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/hardened-steel/ForgeMCP/releases
|
|
9
|
+
Author: ForgeMCP contributors
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
|
+
Requires-Dist: markdown-it-py>=4.0
|
|
14
|
+
Requires-Dist: mcp[cli]==2.1.1
|
|
15
|
+
Requires-Dist: psutil<8,>=7.2
|
|
16
|
+
Requires-Dist: pydantic>=2.13
|
|
17
|
+
Requires-Dist: pywin32>=311; sys_platform == 'win32'
|
|
18
|
+
Requires-Dist: regex>=2024.11.6
|
|
19
|
+
Requires-Dist: starlette>=0.48.0
|
|
20
|
+
Requires-Dist: uvicorn>=0.31.1
|
|
21
|
+
Provides-Extra: build
|
|
22
|
+
Requires-Dist: build>=1.3; extra == 'build'
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build>=1.3; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=9.1; extra == 'dev'
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest>=9.1; extra == 'test'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
1
30
|
# ForgeMCP
|
|
2
31
|
|
|
3
32
|
ForgeMCP is a Python MCP server for structured C and C++ development workflows.
|
|
@@ -71,19 +100,20 @@ and install it in a virtual environment:
|
|
|
71
100
|
|
|
72
101
|
```powershell
|
|
73
102
|
python -m venv .venv
|
|
74
|
-
.\.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
|
|
75
104
|
.\.venv\Scripts\forgemcp.exe --help
|
|
76
105
|
```
|
|
77
106
|
|
|
78
107
|
The wheel includes the compiled HTML widgets and icons. No npm commands or separate
|
|
79
|
-
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
|
|
80
110
|
tools: install the ones needed by your workflows separately.
|
|
81
111
|
The distribution name is `forge-cpp-mcp`; the Python module and server command are `forgemcp`.
|
|
82
112
|
|
|
83
113
|
After the package is published to PyPI, the install command can instead be:
|
|
84
114
|
|
|
85
115
|
```powershell
|
|
86
|
-
.\.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
|
|
87
117
|
```
|
|
88
118
|
|
|
89
119
|
## Setup from source
|
|
@@ -127,6 +157,75 @@ builds the wheel first and then starts the server for this repository.
|
|
|
127
157
|
|
|
128
158
|
All MCP traffic uses stdout. Operational logs must go to stderr.
|
|
129
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
|
+
|
|
130
229
|
## Connect Codex or Claude Code
|
|
131
230
|
|
|
132
231
|
After [installation](#install-a-release) or [source setup](#setup-from-source), copy the appropriate example into the root of the C/C++
|
|
@@ -1,28 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: forge-cpp-mcp
|
|
3
|
-
Version: 0.2.5
|
|
4
|
-
Summary: A safe MCP server for structured C++ project development workflows.
|
|
5
|
-
Project-URL: Homepage, https://github.com/hardened-steel/ForgeMCP
|
|
6
|
-
Project-URL: Repository, https://github.com/hardened-steel/ForgeMCP
|
|
7
|
-
Project-URL: Issues, https://github.com/hardened-steel/ForgeMCP/issues
|
|
8
|
-
Project-URL: Changelog, https://github.com/hardened-steel/ForgeMCP/releases
|
|
9
|
-
Author: ForgeMCP contributors
|
|
10
|
-
License-Expression: MIT
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Python: >=3.13
|
|
13
|
-
Requires-Dist: markdown-it-py>=4.0
|
|
14
|
-
Requires-Dist: mcp[cli]==2.1.1
|
|
15
|
-
Requires-Dist: pydantic>=2.13
|
|
16
|
-
Requires-Dist: regex>=2024.11.6
|
|
17
|
-
Provides-Extra: build
|
|
18
|
-
Requires-Dist: build>=1.3; extra == 'build'
|
|
19
|
-
Provides-Extra: dev
|
|
20
|
-
Requires-Dist: build>=1.3; extra == 'dev'
|
|
21
|
-
Requires-Dist: pytest>=9.1; extra == 'dev'
|
|
22
|
-
Provides-Extra: test
|
|
23
|
-
Requires-Dist: pytest>=9.1; extra == 'test'
|
|
24
|
-
Description-Content-Type: text/markdown
|
|
25
|
-
|
|
26
1
|
# ForgeMCP
|
|
27
2
|
|
|
28
3
|
ForgeMCP is a Python MCP server for structured C and C++ development workflows.
|
|
@@ -96,19 +71,20 @@ and install it in a virtual environment:
|
|
|
96
71
|
|
|
97
72
|
```powershell
|
|
98
73
|
python -m venv .venv
|
|
99
|
-
.\.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
|
|
100
75
|
.\.venv\Scripts\forgemcp.exe --help
|
|
101
76
|
```
|
|
102
77
|
|
|
103
78
|
The wheel includes the compiled HTML widgets and icons. No npm commands or separate
|
|
104
|
-
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
|
|
105
81
|
tools: install the ones needed by your workflows separately.
|
|
106
82
|
The distribution name is `forge-cpp-mcp`; the Python module and server command are `forgemcp`.
|
|
107
83
|
|
|
108
84
|
After the package is published to PyPI, the install command can instead be:
|
|
109
85
|
|
|
110
86
|
```powershell
|
|
111
|
-
.\.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
|
|
112
88
|
```
|
|
113
89
|
|
|
114
90
|
## Setup from source
|
|
@@ -152,6 +128,75 @@ builds the wheel first and then starts the server for this repository.
|
|
|
152
128
|
|
|
153
129
|
All MCP traffic uses stdout. Operational logs must go to stderr.
|
|
154
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
|
+
|
|
155
200
|
## Connect Codex or Claude Code
|
|
156
201
|
|
|
157
202
|
After [installation](#install-a-release) or [source setup](#setup-from-source), copy the appropriate example into the root of the C/C++
|
|
@@ -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
|
+
}
|