Keep HTTP MCP startup independent of ambient WebSocket packages
This commit is contained in:
1 parent
e84362b999
commit
b0340869a7
12 files changed
+207
-38
No files matched your search
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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()
|
||||||
Reference in new issue
Block a user