answer42 0.4.97__tar.gz → 0.4.98__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.
- {answer42-0.4.97 → answer42-0.4.98}/PKG-INFO +24 -1
- {answer42-0.4.97 → answer42-0.4.98}/README.md +23 -0
- {answer42-0.4.97 → answer42-0.4.98}/pyproject.toml +1 -1
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/runtime.py +20 -4
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/server.py +68 -2
- {answer42-0.4.97 → answer42-0.4.98}/.gitignore +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/LICENSE +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/credentials.example.json +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/docs/agent-installation.md +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/docs/architecture.md +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/docs/assets/answer42-logo.png +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/docs/assets/platform42-logo.svg +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/docs/installation.md +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/scripts/build_cf.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/scripts/build_pages.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/scripts/e2e_http_smoke.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/scripts/e2e_stable.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/scripts/openclaw_mcp_autoreload.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/scripts/rag_cli.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/cf/Configuration.xml +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl" +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/client_cf/Catalogs//320/237/320/241_/320/222/320/273/320/260/320/264/320/265/320/273/320/265/321/206.xml" +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/client_cf/Catalogs//320/237/320/241_/320/241/320/277/321/200/320/260/320/262/320/276/321/207/320/275/320/270/320/2721.xml" +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/client_cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/client_cf/Configuration.xml +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/client_cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Ext/ManagerModule.bsl" +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Ext/ObjectModule.bsl" +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Templates//320/236/321/201/320/275/320/276/320/262/320/275/320/260/321/217/320/241/321/205/320/265/320/274/320/260/320/232/320/276/320/274/320/277/320/276/320/275/320/276/320/262/320/272/320/270/320/224/320/260/320/275/320/275/321/213/321/205/Ext/Template.xml" +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Templates//320/236/321/201/320/275/320/276/320/262/320/275/320/260/321/217/320/241/321/205/320/265/320/274/320/260/320/232/320/276/320/274/320/277/320/276/320/275/320/276/320/262/320/272/320/270/320/224/320/260/320/275/320/275/321/213/321/205.xml" +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262.xml" +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/__init__.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/assets/__init__.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/assets/skills/answer42-universal-report/SKILL.md +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/auth.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/bridge.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/credentials.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/os_support.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/platform.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/protocol.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/rag/__init__.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/rag/detect.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/rag/dump.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/rag/model.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/rag/parsers.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/rag/service.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/rag/store.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/rag/summary_backend.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/rag/summary_vector.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/recorder.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/release_helper.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/skill_installer.py +0 -0
- {answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/window_control.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: answer42
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.98
|
|
4
4
|
Summary: Answer42 — The Answer to Life, Universe, and 1C — UI Driver. MCP-powered 1C:Enterprise UI automation: click, fill, navigate, test, and introspect managed forms through the test-client API
|
|
5
5
|
Author: Marvin (AI Assistant), 42Clouds, and contributors
|
|
6
6
|
Author-email: "Kosolapov Stanislav (proDOOMman)" <prodoomman@gmail.com>
|
|
@@ -162,6 +162,29 @@ answer42 install-skills --dry-run --agent all
|
|
|
162
162
|
4. зарегистрируйте MCP-сервер в агентском клиенте;
|
|
163
163
|
5. проверьте установку через `credentials_check`, затем smoke-сессию `start_session` → `active_window` → `stop_session`.
|
|
164
164
|
|
|
165
|
+
## Linux display prerequisite
|
|
166
|
+
|
|
167
|
+
На Linux `start_session` **всегда** запускает два изолированных Xvfb-дисплея: один для test manager и второй для test client. Это намеренно не использует существующий `DISPLAY`, даже если Answer42 запущен в живой X11-сессии: автоматизация и её скриншоты не должны захватывать пользовательский рабочий стол.
|
|
168
|
+
|
|
169
|
+
Поэтому пакет `xvfb` обязателен и для desktop-, и для headless-установок:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Debian / Ubuntu
|
|
173
|
+
sudo apt install xvfb
|
|
174
|
+
|
|
175
|
+
# Fedora / RHEL
|
|
176
|
+
sudo dnf install xorg-x11-server-Xvfb
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Если Xvfb отсутствует, `start_session` завершается до создания ресурсов с понятной подсказкой по установке.
|
|
180
|
+
|
|
181
|
+
## Скриншоты: stdio и StreamableHTTP
|
|
182
|
+
|
|
183
|
+
`screenshot` сохраняет PNG на MCP-хосте и больше не помещает его base64-представление в результат tool-call.
|
|
184
|
+
|
|
185
|
+
- В **stdio** возвращаются только `path`, размер и диагностические поля. Агентский хост при необходимости прикладывает файл по пути.
|
|
186
|
+
- В **StreamableHTTP** добавляется непрозрачная одноразовая ссылка `url` на PNG. Она действует один час по умолчанию (`ONEC_MCP_SCREENSHOT_URL_TTL_SECONDS`), исчезает после первого скачивания и существует не дольше процесса сервера. Для внешнего reverse proxy укажите публичный base URL через `ONEC_MCP_SCREENSHOT_URL_BASE`.
|
|
187
|
+
|
|
165
188
|
## Транспорты: stdio и StreamableHTTP
|
|
166
189
|
|
|
167
190
|
По умолчанию Answer42 использует stdio MCP transport (запускается MCP-хостом как subprocess). Для удалённого deployment доступен **StreamableHTTP** режим с MCP auth:
|
|
@@ -128,6 +128,29 @@ answer42 install-skills --dry-run --agent all
|
|
|
128
128
|
4. зарегистрируйте MCP-сервер в агентском клиенте;
|
|
129
129
|
5. проверьте установку через `credentials_check`, затем smoke-сессию `start_session` → `active_window` → `stop_session`.
|
|
130
130
|
|
|
131
|
+
## Linux display prerequisite
|
|
132
|
+
|
|
133
|
+
На Linux `start_session` **всегда** запускает два изолированных Xvfb-дисплея: один для test manager и второй для test client. Это намеренно не использует существующий `DISPLAY`, даже если Answer42 запущен в живой X11-сессии: автоматизация и её скриншоты не должны захватывать пользовательский рабочий стол.
|
|
134
|
+
|
|
135
|
+
Поэтому пакет `xvfb` обязателен и для desktop-, и для headless-установок:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Debian / Ubuntu
|
|
139
|
+
sudo apt install xvfb
|
|
140
|
+
|
|
141
|
+
# Fedora / RHEL
|
|
142
|
+
sudo dnf install xorg-x11-server-Xvfb
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Если Xvfb отсутствует, `start_session` завершается до создания ресурсов с понятной подсказкой по установке.
|
|
146
|
+
|
|
147
|
+
## Скриншоты: stdio и StreamableHTTP
|
|
148
|
+
|
|
149
|
+
`screenshot` сохраняет PNG на MCP-хосте и больше не помещает его base64-представление в результат tool-call.
|
|
150
|
+
|
|
151
|
+
- В **stdio** возвращаются только `path`, размер и диагностические поля. Агентский хост при необходимости прикладывает файл по пути.
|
|
152
|
+
- В **StreamableHTTP** добавляется непрозрачная одноразовая ссылка `url` на PNG. Она действует один час по умолчанию (`ONEC_MCP_SCREENSHOT_URL_TTL_SECONDS`), исчезает после первого скачивания и существует не дольше процесса сервера. Для внешнего reverse proxy укажите публичный base URL через `ONEC_MCP_SCREENSHOT_URL_BASE`.
|
|
153
|
+
|
|
131
154
|
## Транспорты: stdio и StreamableHTTP
|
|
132
155
|
|
|
133
156
|
По умолчанию Answer42 использует stdio MCP transport (запускается MCP-хостом как subprocess). Для удалённого deployment доступен **StreamableHTTP** режим с MCP auth:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "answer42"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.98"
|
|
4
4
|
description = "Answer42 — The Answer to Life, Universe, and 1C — UI Driver. MCP-powered 1C:Enterprise UI automation: click, fill, navigate, test, and introspect managed forms through the test-client API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
Binary file
|
|
Binary file
|
|
@@ -118,13 +118,29 @@ def find_free_port(start: int = DEFAULT_WS_PORT, end: int | None = None) -> int:
|
|
|
118
118
|
raise RuntimeError(f"No free port found in range {start}..{end}")
|
|
119
119
|
|
|
120
120
|
|
|
121
|
-
def
|
|
122
|
-
"""
|
|
121
|
+
def require_xvfb() -> str:
|
|
122
|
+
"""Return the Xvfb executable or raise an actionable Linux prerequisite error.
|
|
123
|
+
|
|
124
|
+
Answer42 deliberately runs the test manager and test client on two isolated
|
|
125
|
+
X displays. It must therefore have Xvfb even when the MCP process already
|
|
126
|
+
has access to a live desktop ``DISPLAY``.
|
|
127
|
+
"""
|
|
123
128
|
if os_support.IS_WINDOWS:
|
|
124
129
|
raise RuntimeError("Xvfb is not available on Windows")
|
|
125
130
|
xvfb_bin = shutil.which("Xvfb") or "/usr/bin/Xvfb"
|
|
126
|
-
if not Path(xvfb_bin).
|
|
127
|
-
raise
|
|
131
|
+
if not Path(xvfb_bin).is_file():
|
|
132
|
+
raise RuntimeError(
|
|
133
|
+
"Xvfb is required to start an Answer42 session on Linux, including on a host with an active X11 desktop. "
|
|
134
|
+
"Answer42 starts isolated displays for the test manager and test client. "
|
|
135
|
+
"Install the 'xvfb' package (Debian/Ubuntu: sudo apt install xvfb; Fedora/RHEL: sudo dnf install xorg-x11-server-Xvfb) "
|
|
136
|
+
"and retry start_session."
|
|
137
|
+
)
|
|
138
|
+
return xvfb_bin
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def start_xvfb(display: str, resolution: str = DEFAULT_DISPLAY_RES) -> subprocess.Popen:
|
|
142
|
+
"""Start Xvfb for the given display and return the Popen handle."""
|
|
143
|
+
xvfb_bin = require_xvfb()
|
|
128
144
|
proc = subprocess.Popen(
|
|
129
145
|
[xvfb_bin, display, "-screen", "0", resolution, "-ac", "+extension", "GLX", "+render", "-noreset"],
|
|
130
146
|
stdout=subprocess.DEVNULL,
|
|
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import asyncio
|
|
5
|
-
import base64
|
|
6
5
|
import copy
|
|
7
6
|
import hashlib
|
|
8
7
|
import importlib.resources
|
|
@@ -29,6 +28,7 @@ from mcp.server import MCPServer
|
|
|
29
28
|
from mcp.server.lowlevel import NotificationOptions
|
|
30
29
|
from mcp.server.mcpserver.resources import FileResource
|
|
31
30
|
from mcp.server.subscriptions import ToolsListChanged
|
|
31
|
+
from starlette.responses import FileResponse, PlainTextResponse
|
|
32
32
|
|
|
33
33
|
from . import __version__, os_support, release_helper, runtime, skill_installer
|
|
34
34
|
from . import platform as _platform
|
|
@@ -84,6 +84,11 @@ FORM_HINTS_LOCK = os_support.runtime_path("ONEC_MCP_FORM_HINTS_LOCK", "form-hint
|
|
|
84
84
|
UI_TREE_RESOURCE_AUTO_THRESHOLD_TOKENS = int(os.getenv("ONEC_MCP_UI_TREE_RESOURCE_AUTO_THRESHOLD_TOKENS", "50000"))
|
|
85
85
|
UI_TREE_RESOURCE_AUTO_THRESHOLD_BYTES = int(os.getenv("ONEC_MCP_UI_TREE_RESOURCE_AUTO_THRESHOLD_BYTES", "0")) # deprecated fallback; 0 disables
|
|
86
86
|
NOTIFY_TOOLS_CHANGED_ON_STARTUP = os.getenv("ONEC_MCP_NOTIFY_TOOLS_CHANGED_ON_STARTUP", "1").strip().lower() not in {"0", "false", "no", "off"}
|
|
87
|
+
SCREENSHOT_URL_TTL_SECONDS = max(1, int(os.getenv("ONEC_MCP_SCREENSHOT_URL_TTL_SECONDS", "3600")))
|
|
88
|
+
# Set only in StreamableHTTP mode. stdio deliberately returns filesystem paths
|
|
89
|
+
# and never serializes screenshot bytes into an MCP tool response.
|
|
90
|
+
_SCREENSHOT_URL_BASE = ""
|
|
91
|
+
_SCREENSHOT_URLS: dict[str, tuple[Path, float]] = {}
|
|
87
92
|
|
|
88
93
|
|
|
89
94
|
def _float_env(names: str | tuple[str, ...], default: float) -> float:
|
|
@@ -5210,6 +5215,13 @@ async def start_session(
|
|
|
5210
5215
|
if sid in _SESSIONS:
|
|
5211
5216
|
raise ValueError(f"Session {sid!r} already exists.")
|
|
5212
5217
|
|
|
5218
|
+
# A desktop DISPLAY is intentionally not reused: two isolated displays
|
|
5219
|
+
# prevent an interactive user desktop from becoming part of automation or
|
|
5220
|
+
# screenshots. Check the prerequisite before allocating any session state
|
|
5221
|
+
# so a missing package produces a clear error rather than FileNotFoundError.
|
|
5222
|
+
if not os_support.IS_WINDOWS:
|
|
5223
|
+
runtime.require_xvfb()
|
|
5224
|
+
|
|
5213
5225
|
data_dir = os_support.default_data_dir() / "sessions" / sid
|
|
5214
5226
|
with _port_allocation_lock():
|
|
5215
5227
|
port_offset = _stable_session_port_offset(sid)
|
|
@@ -10972,6 +10984,42 @@ async def _dev_eval(
|
|
|
10972
10984
|
return await _bridge_call_recorded(session_id, "dev_eval", {"code": code})
|
|
10973
10985
|
|
|
10974
10986
|
|
|
10987
|
+
def _prune_expired_screenshot_urls(now: float | None = None) -> None:
|
|
10988
|
+
now = time.time() if now is None else now
|
|
10989
|
+
for token, (_, expires_at) in list(_SCREENSHOT_URLS.items()):
|
|
10990
|
+
if expires_at <= now:
|
|
10991
|
+
_SCREENSHOT_URLS.pop(token, None)
|
|
10992
|
+
|
|
10993
|
+
|
|
10994
|
+
@mcp.custom_route("/_answer42/screenshots/{token}", methods=["GET"], include_in_schema=False)
|
|
10995
|
+
async def _download_screenshot(request: Any) -> Any:
|
|
10996
|
+
"""Serve a single-use opaque StreamableHTTP screenshot link."""
|
|
10997
|
+
token = str(request.path_params.get("token") or "")
|
|
10998
|
+
_prune_expired_screenshot_urls()
|
|
10999
|
+
entry = _SCREENSHOT_URLS.pop(token, None)
|
|
11000
|
+
if entry is None:
|
|
11001
|
+
return PlainTextResponse("Screenshot link is expired or unavailable.", status_code=404)
|
|
11002
|
+
image_path, expires_at = entry
|
|
11003
|
+
if expires_at <= time.time() or not image_path.is_file():
|
|
11004
|
+
return PlainTextResponse("Screenshot link is expired or unavailable.", status_code=404)
|
|
11005
|
+
return FileResponse(image_path, media_type="image/png", filename=image_path.name)
|
|
11006
|
+
|
|
11007
|
+
|
|
11008
|
+
def _register_screenshot_url(path: Path) -> dict[str, Any] | None:
|
|
11009
|
+
"""Expose an opaque, time-limited screenshot URL in StreamableHTTP mode."""
|
|
11010
|
+
if not _SCREENSHOT_URL_BASE:
|
|
11011
|
+
return None
|
|
11012
|
+
_prune_expired_screenshot_urls()
|
|
11013
|
+
token = uuid.uuid4().hex + uuid.uuid4().hex
|
|
11014
|
+
expires_at = time.time() + SCREENSHOT_URL_TTL_SECONDS
|
|
11015
|
+
_SCREENSHOT_URLS[token] = (path.resolve(), expires_at)
|
|
11016
|
+
return {
|
|
11017
|
+
"url": f"{_SCREENSHOT_URL_BASE}/_answer42/screenshots/{token}",
|
|
11018
|
+
"url_expires_at": datetime.fromtimestamp(expires_at, UTC).isoformat(),
|
|
11019
|
+
"url_ttl_seconds": SCREENSHOT_URL_TTL_SECONDS,
|
|
11020
|
+
}
|
|
11021
|
+
|
|
11022
|
+
|
|
10975
11023
|
@mcp.tool()
|
|
10976
11024
|
def screenshot(
|
|
10977
11025
|
path: Annotated[str, "Output image path"] = "build/screenshots/screenshot.png",
|
|
@@ -10982,6 +11030,9 @@ def screenshot(
|
|
|
10982
11030
|
) -> dict[str, Any]:
|
|
10983
11031
|
"""Capture a screenshot from the MCP host. Defaults to the active visible 1C window.
|
|
10984
11032
|
|
|
11033
|
+
The response always contains the saved path, never image base64. In
|
|
11034
|
+
StreamableHTTP mode it additionally contains an opaque download URL that
|
|
11035
|
+
expires after one hour by default (``ONEC_MCP_SCREENSHOT_URL_TTL_SECONDS``).
|
|
10985
11036
|
Screenshots are for user evidence and UI diagnostics only. Do not use them
|
|
10986
11037
|
to analyze report/table/tabular-document/list data; use structured tools
|
|
10987
11038
|
such as tabular_document_text, tabular_documents, table_rows,
|
|
@@ -11040,7 +11091,8 @@ def screenshot(
|
|
|
11040
11091
|
) from exc
|
|
11041
11092
|
|
|
11042
11093
|
data = output_path.read_bytes()
|
|
11043
|
-
result = {"path": str(output_path), "bytes": len(data)
|
|
11094
|
+
result: dict[str, Any] = {"path": str(output_path), "bytes": len(data)}
|
|
11095
|
+
result.update(_register_screenshot_url(output_path) or {})
|
|
11044
11096
|
if window:
|
|
11045
11097
|
result["window_geometry"] = geometry
|
|
11046
11098
|
if fallback:
|
|
@@ -11103,6 +11155,13 @@ async def _run_http(args: argparse.Namespace) -> None:
|
|
|
11103
11155
|
stateless_http=args.http_stateless,
|
|
11104
11156
|
host=args.http_host,
|
|
11105
11157
|
)
|
|
11158
|
+
|
|
11159
|
+
# Image data is not placed into MCP tool results. The custom route is
|
|
11160
|
+
# registered at module load because the MCP SDK builds the Starlette app
|
|
11161
|
+
# from its custom-route list. The opaque token makes the URL unguessable;
|
|
11162
|
+
# the short lifetime and single use bound access.
|
|
11163
|
+
global _SCREENSHOT_URL_BASE
|
|
11164
|
+
_SCREENSHOT_URL_BASE = (args.http_public_base_url or f"http://{args.http_host}:{args.http_port}").rstrip("/")
|
|
11106
11165
|
config_uvicorn = uvicorn.Config(
|
|
11107
11166
|
app,
|
|
11108
11167
|
host=args.http_host,
|
|
@@ -11114,6 +11173,8 @@ async def _run_http(args: argparse.Namespace) -> None:
|
|
|
11114
11173
|
try:
|
|
11115
11174
|
await server.serve()
|
|
11116
11175
|
finally:
|
|
11176
|
+
_SCREENSHOT_URLS.clear()
|
|
11177
|
+
_SCREENSHOT_URL_BASE = ""
|
|
11117
11178
|
if tools_changed_task is not None:
|
|
11118
11179
|
tools_changed_task.cancel()
|
|
11119
11180
|
with suppress(asyncio.CancelledError):
|
|
@@ -11329,6 +11390,11 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
11329
11390
|
default=int(os.getenv("ONEC_MCP_HTTP_PORT", "8080")),
|
|
11330
11391
|
help="port to bind the StreamableHTTP server to",
|
|
11331
11392
|
)
|
|
11393
|
+
parser.add_argument(
|
|
11394
|
+
"--http-public-base-url",
|
|
11395
|
+
default=os.getenv("ONEC_MCP_SCREENSHOT_URL_BASE", ""),
|
|
11396
|
+
help="public base URL used in temporary StreamableHTTP screenshot links; useful behind a reverse proxy",
|
|
11397
|
+
)
|
|
11332
11398
|
parser.add_argument(
|
|
11333
11399
|
"--http-token",
|
|
11334
11400
|
default=os.getenv("ONEC_MCP_HTTP_TOKEN", ""),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{answer42-0.4.97 → answer42-0.4.98}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{answer42-0.4.97 → answer42-0.4.98}/src/mcp_1c/assets/skills/answer42-universal-report/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|