Keep HTTP MCP startup independent of ambient WebSocket packages

This commit is contained in:
FrameBridge contributors committed 2026-10-27 15:30:25 -04:00
1 parent e84362b999
commit b0340869a7
12 files changed
+207 -38

No files matched your search

+3 -3
View File
@@ -4,7 +4,7 @@
Run a task on your own machine, return to the same Bash terminal, inspect an X11 desktop, or select an enrolled computer. ChatGPT remains hosted; FrameBridge runs the tools on your Linux computers. Run a task on your own machine, return to the same Bash terminal, inspect an X11 desktop, or select an enrolled computer. ChatGPT remains hosted; FrameBridge runs the tools on your Linux computers.
> **Development candidate: `0.3.2~compat.20261027.2`.** This revision is being validated. It is not a signed public release. The separately signed 0.3.1 release remains a different artifact. Read the [candidate scope and validation record](docs/LINUX-COMPATIBILITY-CANDIDATE.md) before choosing a package. > **Development candidate: `0.3.2~compat.20261027.3`.** This revision is being validated. It is not a signed public release. The separately signed 0.3.1 release remains a different artifact. Read the [candidate scope and validation record](docs/LINUX-COMPATIBILITY-CANDIDATE.md) before choosing a package.
## Start here ## Start here
@@ -26,14 +26,14 @@ The compatibility candidate has separate native Python builds; it is not one int
| Ubuntu 24.04 | amd64 / x86_64 | 3.12 | `ubuntu24.04-amd64-py312` | | Ubuntu 24.04 | amd64 / x86_64 | 3.12 | `ubuntu24.04-amd64-py312` |
| Linux Mint 22.x | amd64 / x86_64 | 3.12 | `mint22-amd64-py312` | | Linux Mint 22.x | amd64 / x86_64 | 3.12 | `mint22-amd64-py312` |
The preceding `.1` candidate passed VM tests on these systems, including Cinnamon on Mint 22.3, and a Debian production pilot. This `.2` revision needs its own validation. Ubuntu 26.04 and LMDE 7 builds remain experimental; other distributions, older releases and CPU architectures are outside this candidate's scope. The preceding `.1` candidate passed VM tests on these systems, including Cinnamon on Mint 22.3, and a Debian production pilot. This `.3` revision needs its own validation. Ubuntu 26.04 and LMDE 7 builds remain experimental; other distributions, older releases and CPU architectures are outside this candidate's scope.
Verify official releases with the independently trusted project key as described in [Official releases](OFFICIAL-RELEASES.md). A candidate's recorded SHA-256 identifies its reviewed file; it is not a release signature. Candidate packages are for reviewed pilots. Verify official releases with the independently trusted project key as described in [Official releases](OFFICIAL-RELEASES.md). A candidate's recorded SHA-256 identifies its reviewed file; it is not a release signature. Candidate packages are for reviewed pilots.
For example, with the **Mint** candidate downloaded into the current directory: For example, with the **Mint** candidate downloaded into the current directory:
~~~bash ~~~bash
sudo apt install ./FrameBridge-0.3.2~compat.20261027.2-mint22-amd64-py312.deb sudo apt install ./FrameBridge-0.3.2~compat.20261027.3-mint22-amd64-py312.deb
~~~ ~~~
Use the corresponding profile filename on Debian or Ubuntu. Review APT's proposed changes for the actual computer. Installation places the software on disk; it does not establish a ChatGPT connection or replace the system Python. Use the corresponding profile filename on Debian or Ubuntu. Review APT's proposed changes for the actual computer. Installation places the software on disk; it does not establish a ChatGPT connection or replace the system Python.
+2 -2
View File
@@ -1,6 +1,6 @@
# FrameBridge — command guide # FrameBridge — command guide
Version: 0.3.2~compat.20261027.2 | Guide schema: 1 Version: 0.3.2~compat.20261027.3 | Guide schema: 1
This reference is generated from the shared CLI/MCP guide. Use the matching installed version. This reference is generated from the shared CLI/MCP guide. Use the matching installed version.
@@ -36,7 +36,7 @@ fb --json status
Install the .deb built for your exact Debian 13, Ubuntu 24.04/26.04, Linux Mint 22.x or LMDE 7 amd64 profile. These compatibility candidates still require installation and lifecycle validation on each target. Select the artifact from the matching profile folder and keep the distribution system Python. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal; advanced setup is optional for central/node roles and public settings. Install the .deb built for your exact Debian 13, Ubuntu 24.04/26.04, Linux Mint 22.x or LMDE 7 amd64 profile. These compatibility candidates still require installation and lifecycle validation on each target. Select the artifact from the matching profile folder and keep the distribution system Python. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal; advanced setup is optional for central/node roles and public settings.
~~~sh ~~~sh
sudo apt install ./framebridge-expanded_0.3.2~compat.20261027.2_amd64.deb sudo apt install ./framebridge-expanded_0.3.2~compat.20261027.3_amd64.deb
fb connect fb connect
~~~ ~~~
+23 -23
View File
@@ -1,12 +1,12 @@
{ {
"product": "FrameBridge", "product": "FrameBridge",
"version": "0.3.2~compat.20261027.2", "version": "0.3.2~compat.20261027.3",
"guide_schema": 1, "guide_schema": 1,
"topics": { "topics": {
"start": { "start": {
"ok": true, "ok": true,
"product": "FrameBridge", "product": "FrameBridge",
"version": "0.3.2~compat.20261027.2", "version": "0.3.2~compat.20261027.3",
"guide_schema": 1, "guide_schema": 1,
"topic": "start", "topic": "start",
"title": "Start with FrameBridge", "title": "Start with FrameBridge",
@@ -24,7 +24,7 @@
"heading": "Install and connect", "heading": "Install and connect",
"body": "Install the .deb built for your exact Debian 13, Ubuntu 24.04/26.04, Linux Mint 22.x or LMDE 7 amd64 profile. These compatibility candidates still require installation and lifecycle validation on each target. Select the artifact from the matching profile folder and keep the distribution system Python. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal; advanced setup is optional for central/node roles and public settings.", "body": "Install the .deb built for your exact Debian 13, Ubuntu 24.04/26.04, Linux Mint 22.x or LMDE 7 amd64 profile. These compatibility candidates still require installation and lifecycle validation on each target. Select the artifact from the matching profile folder and keep the distribution system Python. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal; advanced setup is optional for central/node roles and public settings.",
"commands": [ "commands": [
"sudo apt install ./framebridge-expanded_0.3.2~compat.20261027.2_amd64.deb", "sudo apt install ./framebridge-expanded_0.3.2~compat.20261027.3_amd64.deb",
"fb connect" "fb connect"
] ]
}, },
@@ -64,12 +64,12 @@
], ],
"read_only": true, "read_only": true,
"capabilities_verified": false, "capabilities_verified": false,
"guide_sha256": "4a85d9721ac3f758837a57126484b6cca56a598088f90365de2796f0b8d13766" "guide_sha256": "b5be618bfb62d102ebec222370882da129c37e92044ebe86cd9203def5c0b272"
}, },
"commands": { "commands": {
"ok": true, "ok": true,
"product": "FrameBridge", "product": "FrameBridge",
"version": "0.3.2~compat.20261027.2", "version": "0.3.2~compat.20261027.3",
"guide_schema": 1, "guide_schema": 1,
"topic": "commands", "topic": "commands",
"title": "Command reference", "title": "Command reference",
@@ -136,12 +136,12 @@
], ],
"read_only": true, "read_only": true,
"capabilities_verified": false, "capabilities_verified": false,
"guide_sha256": "e8680e094c647dc991d7bc9e4800d3978502dad81cf101f10d072be88dc4e9a1" "guide_sha256": "043a13ee822b03113ca877351986ee4d25628fc31bf623a9f23134c1980545c0"
}, },
"connection": { "connection": {
"ok": true, "ok": true,
"product": "FrameBridge", "product": "FrameBridge",
"version": "0.3.2~compat.20261027.2", "version": "0.3.2~compat.20261027.3",
"guide_schema": 1, "guide_schema": 1,
"topic": "connection", "topic": "connection",
"title": "Connect and disconnect ChatGPT", "title": "Connect and disconnect ChatGPT",
@@ -190,12 +190,12 @@
], ],
"read_only": true, "read_only": true,
"capabilities_verified": false, "capabilities_verified": false,
"guide_sha256": "0c8c16baf4d28e9b37154dad049193aeae252813b18eba3159cf437918eb3416" "guide_sha256": "3a6218c30256375c32bc80b34609f75b8465e901a2c7012389e6b5cdff995af3"
}, },
"lifecycle": { "lifecycle": {
"ok": true, "ok": true,
"product": "FrameBridge", "product": "FrameBridge",
"version": "0.3.2~compat.20261027.2", "version": "0.3.2~compat.20261027.3",
"guide_schema": 1, "guide_schema": 1,
"topic": "lifecycle", "topic": "lifecycle",
"title": "Services, connection intent and boot policy", "title": "Services, connection intent and boot policy",
@@ -256,12 +256,12 @@
], ],
"read_only": true, "read_only": true,
"capabilities_verified": false, "capabilities_verified": false,
"guide_sha256": "0e3d6c96084e40b07d7373f3f7dbe22aa7659e59ec7cff57383026918dd0948c" "guide_sha256": "a7dd26f8acc2ffbadb47f01a9fe24b1b547853f576b7b8805e01ab9d40087cd4"
}, },
"nodes": { "nodes": {
"ok": true, "ok": true,
"product": "FrameBridge", "product": "FrameBridge",
"version": "0.3.2~compat.20261027.2", "version": "0.3.2~compat.20261027.3",
"guide_schema": 1, "guide_schema": 1,
"topic": "nodes", "topic": "nodes",
"title": "Central and node operation", "title": "Central and node operation",
@@ -310,12 +310,12 @@
], ],
"read_only": true, "read_only": true,
"capabilities_verified": false, "capabilities_verified": false,
"guide_sha256": "a0a317d230abb52bd673bb82453c0c17d73d31e30b1766e4247ef80ada02aa2a" "guide_sha256": "a535fb417b3dff1c6ad022a83576ea9e001a2f2f78e99a2ca0d146747fe1300c"
}, },
"terminals": { "terminals": {
"ok": true, "ok": true,
"product": "FrameBridge", "product": "FrameBridge",
"version": "0.3.2~compat.20261027.2", "version": "0.3.2~compat.20261027.3",
"guide_schema": 1, "guide_schema": 1,
"topic": "terminals", "topic": "terminals",
"title": "Persistent Bash terminals", "title": "Persistent Bash terminals",
@@ -376,12 +376,12 @@
], ],
"read_only": true, "read_only": true,
"capabilities_verified": false, "capabilities_verified": false,
"guide_sha256": "e9ab7535894f07a7aa309d0f4f02d36d37c65143006f8bc67b9f12a1e00f0f5a" "guide_sha256": "d6854b43a419e32ba16a3707d1481607d0073017daa971597bbeeee885e22f8c"
}, },
"screens": { "screens": {
"ok": true, "ok": true,
"product": "FrameBridge", "product": "FrameBridge",
"version": "0.3.2~compat.20261027.2", "version": "0.3.2~compat.20261027.3",
"guide_schema": 1, "guide_schema": 1,
"topic": "screens", "topic": "screens",
"title": "Authentic screen observation and input", "title": "Authentic screen observation and input",
@@ -426,12 +426,12 @@
], ],
"read_only": true, "read_only": true,
"capabilities_verified": false, "capabilities_verified": false,
"guide_sha256": "3d37bc0fac316fa50275f1a9f38f15e87aef435593c694aebaacf4666734e1fd" "guide_sha256": "0b43cec434d78c75e277487cd28346c689e6f8111e0d99daf67e03904380651b"
}, },
"diagnostics": { "diagnostics": {
"ok": true, "ok": true,
"product": "FrameBridge", "product": "FrameBridge",
"version": "0.3.2~compat.20261027.2", "version": "0.3.2~compat.20261027.3",
"guide_schema": 1, "guide_schema": 1,
"topic": "diagnostics", "topic": "diagnostics",
"title": "Status and diagnosis", "title": "Status and diagnosis",
@@ -476,12 +476,12 @@
], ],
"read_only": true, "read_only": true,
"capabilities_verified": false, "capabilities_verified": false,
"guide_sha256": "9cdab418287a3fcf283258782d117813bb45d23183101e5b7ee8eeb162cd38b4" "guide_sha256": "67ddeb5dc3314452f3378099b5427bed6e3a426547c322294b29f8935302bc5e"
}, },
"security": { "security": {
"ok": true, "ok": true,
"product": "FrameBridge", "product": "FrameBridge",
"version": "0.3.2~compat.20261027.2", "version": "0.3.2~compat.20261027.3",
"guide_schema": 1, "guide_schema": 1,
"topic": "security", "topic": "security",
"title": "Authorization, distribution and safe operation", "title": "Authorization, distribution and safe operation",
@@ -516,12 +516,12 @@
], ],
"read_only": true, "read_only": true,
"capabilities_verified": false, "capabilities_verified": false,
"guide_sha256": "64ecf0b4e25dec7faffae90ef3d48ce732f5ec2c7fb815815a625ae124230c9e" "guide_sha256": "995ee955506bd5ac91b27363c6833d2f6872555d7b6fbb03471b5af18bbf9661"
}, },
"compatibility": { "compatibility": {
"ok": true, "ok": true,
"product": "FrameBridge", "product": "FrameBridge",
"version": "0.3.2~compat.20261027.2", "version": "0.3.2~compat.20261027.3",
"guide_schema": 1, "guide_schema": 1,
"topic": "compatibility", "topic": "compatibility",
"title": "Compatibility with earlier commands", "title": "Compatibility with earlier commands",
@@ -564,8 +564,8 @@
], ],
"read_only": true, "read_only": true,
"capabilities_verified": false, "capabilities_verified": false,
"guide_sha256": "f622466edc45196d42390129d8a32424e1b0e64b7751688790061b1e8b5941d2" "guide_sha256": "1a31c25cf96ca3f02f74984eba740dade767816537e3fac9edee9d7cebf86717"
} }
}, },
"guide_sha256": "f83c632796c7cdb3489683b1c4584f0fedec11f4aeb9321430f1a709888d6965" "guide_sha256": "05db1724919b95443b00670ef3365512c0ac30bbd15462819c3a2236e7df7a25"
} }
+2 -2
View File
@@ -1,6 +1,6 @@
# FrameBridge — command guide # FrameBridge — command guide
Version: 0.3.2~compat.20261027.2 | Guide schema: 1 Version: 0.3.2~compat.20261027.3 | Guide schema: 1
This reference is generated from the shared CLI/MCP guide. Use the matching installed version. This reference is generated from the shared CLI/MCP guide. Use the matching installed version.
@@ -36,7 +36,7 @@ fb --json status
Install the .deb built for your exact Debian 13, Ubuntu 24.04/26.04, Linux Mint 22.x or LMDE 7 amd64 profile. These compatibility candidates still require installation and lifecycle validation on each target. Select the artifact from the matching profile folder and keep the distribution system Python. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal; advanced setup is optional for central/node roles and public settings. Install the .deb built for your exact Debian 13, Ubuntu 24.04/26.04, Linux Mint 22.x or LMDE 7 amd64 profile. These compatibility candidates still require installation and lifecycle validation on each target. Select the artifact from the matching profile folder and keep the distribution system Python. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal; advanced setup is optional for central/node roles and public settings.
~~~sh ~~~sh
sudo apt install ./framebridge-expanded_0.3.2~compat.20261027.2_amd64.deb sudo apt install ./framebridge-expanded_0.3.2~compat.20261027.3_amd64.deb
fb connect fb connect
~~~ ~~~
+2 -2
View File
@@ -1,6 +1,6 @@
# FrameBridge — Debian, Ubuntu and Linux Mint installation and first use # FrameBridge — Debian, Ubuntu and Linux Mint installation and first use
Version: 0.3.2~compat.20261027.2 | Guide schema: 1 Version: 0.3.2~compat.20261027.3 | Guide schema: 1
This reference is generated from the shared CLI/MCP guide. Use the matching installed version. This reference is generated from the shared CLI/MCP guide. Use the matching installed version.
@@ -36,7 +36,7 @@ fb --json status
Install the .deb built for your exact Debian 13, Ubuntu 24.04/26.04, Linux Mint 22.x or LMDE 7 amd64 profile. These compatibility candidates still require installation and lifecycle validation on each target. Select the artifact from the matching profile folder and keep the distribution system Python. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal; advanced setup is optional for central/node roles and public settings. Install the .deb built for your exact Debian 13, Ubuntu 24.04/26.04, Linux Mint 22.x or LMDE 7 amd64 profile. These compatibility candidates still require installation and lifecycle validation on each target. Select the artifact from the matching profile folder and keep the distribution system Python. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal; advanced setup is optional for central/node roles and public settings.
~~~sh ~~~sh
sudo apt install ./framebridge-expanded_0.3.2~compat.20261027.2_amd64.deb sudo apt install ./framebridge-expanded_0.3.2~compat.20261027.3_amd64.deb
fb connect fb connect
~~~ ~~~
+2 -2
View File
@@ -1,4 +1,4 @@
.TH FRAMEBRIDGE 1 "2026-10-27" "FrameBridge 0.3.2~compat.20261027.2" "User Commands" .TH FRAMEBRIDGE 1 "2026-10-27" "FrameBridge 0.3.2~compat.20261027.3" "User Commands"
.SH NAME .SH NAME
framebridge, fb \- use local Linux tools and enrolled nodes with ChatGPT framebridge, fb \- use local Linux tools and enrolled nodes with ChatGPT
.SH SYNOPSIS .SH SYNOPSIS
@@ -21,7 +21,7 @@ fb \-\-json status
Install the .deb built for your exact Debian 13, Ubuntu 24.04/26.04, Linux Mint 22.x or LMDE 7 amd64 profile. These compatibility candidates still require installation and lifecycle validation on each target. Select the artifact from the matching profile folder and keep the distribution system Python. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal; advanced setup is optional for central/node roles and public settings. Install the .deb built for your exact Debian 13, Ubuntu 24.04/26.04, Linux Mint 22.x or LMDE 7 amd64 profile. These compatibility candidates still require installation and lifecycle validation on each target. Select the artifact from the matching profile folder and keep the distribution system Python. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal; advanced setup is optional for central/node roles and public settings.
.PP .PP
.nf .nf
sudo apt install ./framebridge\-expanded_0.3.2~compat.20261027.2_amd64.deb sudo apt install ./framebridge\-expanded_0.3.2~compat.20261027.3_amd64.deb
fb connect fb connect
.fi .fi
.SS Updating and removal .SS Updating and removal
@@ -1 +1 @@
__version__ = "0.3.2~compat.20261027.2" __version__ = "0.3.2~compat.20261027.3"
@@ -333,7 +333,8 @@ def main() -> None:
streamable_http_path="/mcp") streamable_http_path="/mcp")
hub = FleetHub(discover_nodes, node_read) hub = FleetHub(discover_nodes, node_read)
register_fleet_read_tools(server, hub) register_fleet_read_tools(server, hub)
server.run(transport="streamable-http") from .http_server import run_streamable_mcp
run_streamable_mcp(server)
if __name__ == "__main__": if __name__ == "__main__":
@@ -0,0 +1,22 @@
"""Run FrameBridge's HTTP MCP endpoints without optional WebSocket detection.
The FastMCP app owns its routes, middleware and session-manager lifespan.
Other FrameBridge transports do not use this helper.
"""
def run_streamable_mcp(server):
import anyio
import uvicorn
async def serve():
config = uvicorn.Config(
server.streamable_http_app(),
host=server.settings.host,
port=server.settings.port,
log_level=server.settings.log_level.lower(),
ws="none",
)
await uvicorn.Server(config).serve()
anyio.run(serve)
+1 -1
View File
@@ -11,7 +11,7 @@ from pathlib import Path
import re import re
CANDIDATE_VERSION = "0.3.2~compat.20261027.2" CANDIDATE_VERSION = "0.3.2~compat.20261027.3"
VENDOR_RELATIVE = Path("original/geranas/FrameBridge-Fleet-Hub/vendor") VENDOR_RELATIVE = Path("original/geranas/FrameBridge-Fleet-Hub/vendor")
SOURCE_VENDOR = Path("sources/original-geranas/FrameBridge-Fleet-Hub/vendor") SOURCE_VENDOR = Path("sources/original-geranas/FrameBridge-Fleet-Hub/vendor")
GLIBC_BASELINE = (2, 34) GLIBC_BASELINE = (2, 34)
+2 -1
View File
@@ -838,4 +838,5 @@ from framebridge_expanded.tty_mcp import register_tools as register_tty_tools
register_tty_tools(mcp, framebridge_exec) register_tty_tools(mcp, framebridge_exec)
if __name__ == "__main__": if __name__ == "__main__":
mcp.run(transport="streamable-http") from framebridge_expanded.http_server import run_streamable_mcp
run_streamable_mcp(mcp)
+145
View File
@@ -0,0 +1,145 @@
"""Actual HTTP MCP startup is independent of ambient WebSocket libraries."""
import json
import os
from pathlib import Path
import socket
import subprocess
import sys
import tempfile
import time
import unittest
import urllib.error
import urllib.request
from framebridge_expanded import __version__
SOURCE = Path(__file__).resolve().parents[1]
class HttpMcpTransportTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory(prefix='fb-http-transport-')
self.addCleanup(self.temporary.cleanup)
self.directory = Path(self.temporary.name)
self.ambient = self.directory/'ambient'
package = self.ambient/'websockets'
package.mkdir(parents=True)
self.marker = self.directory/'unwanted-websocket-import'
(package/'__init__.py').write_text(
"import os\nfrom pathlib import Path\n"
"Path(os.environ['FB_TEST_WS_MARKER']).write_text('imported')\n"
"raise RuntimeError('INCOMPATIBLE_AMBIENT_WEBSOCKETS')\n")
home = self.directory/'home'
home.mkdir(mode=0o700)
self.env = dict(os.environ, HOME=str(home), PYTHONNOUSERSITE='1',
PYTHONDONTWRITEBYTECODE='1',
FB_TEST_WS_MARKER=str(self.marker),
FRAMEBRIDGE_EXPANDED_STATE=str(home/'state'),
FRAMEBRIDGE_STATE_DIR=str(home/'state'),
FRAMEBRIDGE_TOOL_ROOT=str(home/'adapter'),
FRAMEBRIDGE_BROKER_SOURCE=str(SOURCE/'sources/original-geranas/FrameBridge-Broker-RC1/src'),
FRAMEBRIDGE_FLEET_SOURCE=str(SOURCE/'sources/original-geranas/FrameBridge-Fleet-Hub/app'))
# Match the child search path exported by runtime.environment(), with an
# intentionally incompatible optional library taking precedence.
self.env['PYTHONPATH'] = os.pathsep.join(map(str, [
self.ambient, SOURCE/'expanded', SOURCE/'portable',
SOURCE/'sources/original-geranas/FrameBridge-Fleet-Hub/vendor']))
def test_default_websocket_detection_would_load_the_incompatible_library(self):
code = "from mcp.server.fastmcp import FastMCP; import uvicorn; uvicorn.Config(FastMCP('negative-control').streamable_http_app()).load()"
result = subprocess.run([sys.executable, '-c', code], env=self.env,
capture_output=True, text=True, timeout=15)
self.assertNotEqual(0, result.returncode)
self.assertIn('INCOMPATIBLE_AMBIENT_WEBSOCKETS', result.stderr)
self.assertTrue(self.marker.exists())
def start(self, name):
with socket.socket() as reservation:
reservation.bind(('127.0.0.1', 0))
self.port = reservation.getsockname()[1]
if name == 'fleet':
command = [sys.executable, '-m', 'framebridge_expanded.fleet']
self.env['FRAMEBRIDGE_FLEET_HUB_PORT'] = str(self.port)
else:
command = [sys.executable, str(SOURCE/'portable/front_door_mcp.py')]
self.env['FRAMEBRIDGE_FRONT_DOOR_PORT'] = str(self.port)
log = (self.directory/(name+'.log')).open('w')
self.process = subprocess.Popen(command, env=self.env, cwd=self.directory,
stdin=subprocess.DEVNULL, stdout=log,
stderr=log, start_new_session=True)
log.close()
self.addCleanup(self.stop)
self.session = None
self.protocol = None
deadline = time.monotonic()+12
while time.monotonic() < deadline:
if self.process.poll() is not None:
self.fail('HTTP component exited: '+(self.directory/(name+'.log')).read_text()[-2000:])
try:
reply, headers, _ = self.rpc({'jsonrpc':'2.0', 'id':1,
'method':'initialize', 'params':{'protocolVersion':'2025-11-25',
'capabilities':{}, 'clientInfo':{'name':'framebridge-http-qa','version':'1'}}})
break
except urllib.error.URLError:
time.sleep(.05)
else:
self.fail('HTTP component did not become ready')
self.assertIn('result', reply)
self.assertIn('serverInfo', reply['result'])
self.assertTrue(headers.get('Content-Type', '').startswith('text/event-stream'))
self.session = headers.get('Mcp-Session-Id')
self.assertTrue(self.session)
self.protocol = reply['result']['protocolVersion']
_, _, code = self.rpc({'jsonrpc':'2.0', 'method':'notifications/initialized'})
self.assertEqual(202, code)
def stop(self):
if self.process.poll() is None:
self.process.terminate()
try:
self.process.wait(timeout=5)
except subprocess.TimeoutExpired:
self.process.kill()
self.process.wait(timeout=5)
def rpc(self, value):
headers = {'Content-Type':'application/json',
'Accept':'application/json, text/event-stream'}
if self.session:
headers['Mcp-Session-Id'] = self.session
if self.protocol:
headers['Mcp-Protocol-Version'] = self.protocol
request = urllib.request.Request('http://127.0.0.1:'+str(self.port)+'/mcp',
data=json.dumps(value).encode(), headers=headers, method='POST')
with urllib.request.urlopen(request, timeout=3) as response:
raw = response.read().decode()
if raw and response.headers.get('Content-Type','').startswith('text/event-stream'):
raw = next(line[5:].strip() for line in raw.splitlines() if line.startswith('data:'))
return json.loads(raw) if raw else None, response.headers, response.status
def test_fleet_initializes_and_lists_tools_without_websocket_import(self):
self.start('fleet')
result, _, status = self.rpc({'jsonrpc':'2.0','id':2,'method':'tools/list'})
self.assertEqual(200, status)
self.assertEqual(6, len(result['result']['tools']))
self.assertFalse(self.marker.exists())
def test_actual_frontdoor_help_and_execution_denial_over_streamable_http(self):
self.start('frontdoor')
result, _, status = self.rpc({'jsonrpc':'2.0','id':2,'method':'tools/list'})
self.assertEqual(200, status)
names = {tool['name'] for tool in result['result']['tools']}
self.assertTrue({'framebridge_help','framebridge_exec'}.issubset(names))
reply, _, _ = self.rpc({'jsonrpc':'2.0','id':3,'method':'tools/call',
'params':{'name':'framebridge_help','arguments':{'topic':'start'}}})
self.assertIn(__version__, json.dumps(reply['result']))
denied, _, _ = self.rpc({'jsonrpc':'2.0','id':4,'method':'tools/call',
'params':{'name':'framebridge_exec',
'arguments':{'script':'true','authorized':False}}})
self.assertIn('AUTHORIZED_TRUE_REQUIRED', json.dumps(denied['result']))
self.assertFalse((Path(self.env['FRAMEBRIDGE_TOOL_ROOT'])/'adapter.sock').exists())
self.assertFalse(self.marker.exists())
if __name__ == '__main__':
unittest.main()