Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/fractal-chatgpt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ parking_lot = "0.12"
name = "fractal-chatgpt"
path = "src/main.rs"

# Keep the existing Nix package buildable until its package-name cutover.
# LIVE-MIGRATION BRIDGE arn:lmig:smalltalk:2026-11-01-fractal-chatgpt-alias — DELETE at contraction — https://github.com/compoundingtech/fractal/issues/3032
[[bin]]
name = "st-chatgpt"
path = "src/main.rs"
# LIVE-MIGRATION END arn:lmig:smalltalk:2026-11-01-fractal-chatgpt-alias
6 changes: 3 additions & 3 deletions crates/fractal-chatgpt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ impl CallObservation {
}
impl Drop for CallObservation {
fn drop(&mut self) {
tracing::info!(target: "st_chatgpt::call",
tracing::info!(target: "fractal_chatgpt::call",
tool = self.tool, outcome = self.outcome, code = self.code,
daemon_request_id = self.request_id.as_deref(),
duration_ms = self.started.elapsed().as_secs_f64() * 1000.0,
Expand Down Expand Up @@ -287,7 +287,7 @@ async fn dispatch(adapter: &Adapter, name: &str, args: &Value, observation: &mut
let query: Vec<_> = object.iter().map(|(k,v)| (k.as_str(),v.as_str().map(str::to_owned).unwrap_or_else(||v.to_string()))).collect();
adapter.client.get(url).query(&query)
};
let response = request.bearer_auth(&adapter.bearer).send().await.map_err(|_| json!({"code":"unavailable","message":"The paired st daemon is unavailable."}))?;
let response = request.bearer_auth(&adapter.bearer).send().await.map_err(|_| json!({"code":"unavailable","message":"The paired Fractal daemon is unavailable."}))?;
let status = response.status();
// reqwest's JSON decoder already buffers the body. Reuse that exact buffer
// for byte accounting, without reserializing content or trusting Content-Length.
Expand All @@ -300,7 +300,7 @@ async fn dispatch(adapter: &Adapter, name: &str, args: &Value, observation: &mut
impl ServerHandler for Adapter {
fn get_info(&self) -> ServerConfig {
ServerConfig::new(ServerCapabilities::builder().enable_tools().build())
.with_server_info(Implementation::new("st-chatgpt",env!("CARGO_PKG_VERSION")))
.with_server_info(Implementation::new("fractal-chatgpt",env!("CARGO_PKG_VERSION")))
}
async fn list_tools(&self, _:Option<PaginatedRequestParams>, _:RequestContext<RoleServer>) -> Result<ListToolsResult,ErrorData> {
Ok(ListToolsResult { tools: tool_definitions(), ..Default::default() })
Expand Down
23 changes: 22 additions & 1 deletion crates/fractal-chatgpt/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::{net::SocketAddr, path::PathBuf};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};

#[derive(Parser)]
#[command(name = "fractal-chatgpt")]
struct Args {
#[arg(long)]
socket: PathBuf,
Expand All @@ -17,6 +18,24 @@ struct Args {
listen: SocketAddr,
}

fn is_call_telemetry_target(target: &str) -> bool {
let permitted = target == "fractal_chatgpt::call";
// LIVE-MIGRATION BRIDGE arn:lmig:smalltalk:2026-11-07-fractal-chatgpt-telemetry-target — DELETE at contraction — https://github.com/compoundingtech/fractal/issues/3032
// Deployments must enable both targets in RUST_LOG before upgrading.
let permitted = permitted || target == "st_chatgpt::call";
// LIVE-MIGRATION END arn:lmig:smalltalk:2026-11-07-fractal-chatgpt-telemetry-target
permitted
}

#[test]
fn call_telemetry_target_allowlist() {
assert!(is_call_telemetry_target("fractal_chatgpt::call"));
// LIVE-MIGRATION BRIDGE arn:lmig:smalltalk:2026-11-07-fractal-chatgpt-telemetry-target — DELETE at contraction — https://github.com/compoundingtech/fractal/issues/3032
assert!(is_call_telemetry_target("st_chatgpt::call"));
// LIVE-MIGRATION END arn:lmig:smalltalk:2026-11-07-fractal-chatgpt-telemetry-target
assert!(!is_call_telemetry_target("reqwest::connect"));
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
Expand All @@ -27,7 +46,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer().with_ansi(false)
.with_writer(std::io::stderr)
.with_filter(tracing_subscriber::filter::filter_fn(|metadata| metadata.target() == "st_chatgpt::call"))
.with_filter(tracing_subscriber::filter::filter_fn(|metadata| {
is_call_telemetry_target(metadata.target())
}))
.with_filter(filter))
.try_init()?;
if !args.listen.ip().is_loopback() {
Expand Down
13 changes: 10 additions & 3 deletions crates/fractal-chatgpt/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ use tower::ServiceExt;

fn disconnected_adapter() -> Adapter {
Adapter {
client: reqwest::Client::builder().unix_socket("/nonexistent/st-chatgpt-tests/st3-client.sock").build().unwrap(),
client: reqwest::Client::builder().unix_socket("/nonexistent/fractal-chatgpt-tests/fractal-client.sock").build().unwrap(),
bearer: "test-only-not-a-daemon-credential".into(),
}
}

#[test]
fn server_identity_uses_fractal_chatgpt() {
assert_eq!(disconnected_adapter().get_info().server_info.name, "fractal-chatgpt");
}

#[test]
fn adapter_accepts_only_paired_gateway_socket_names() {
let dir = tempfile::tempdir().unwrap();
Expand Down Expand Up @@ -511,7 +516,7 @@ async fn dispatch_telemetry_is_correlated_and_content_free() {
.with_writer(logs.clone()).finish();
let _capture = tracing::subscriber::set_default(subscriber);
let dir = tempfile::tempdir().unwrap();
let socket = dir.path().join("st3-client.sock");
let socket = dir.path().join("fractal-client.sock");
let listener = tokio::net::UnixListener::bind(&socket).unwrap();
let id = "request/12345678-1234-1234-1234-123456789abc";
let body = json!({"request_id":id,"value":{"content":"RESPONSE_CONTENT_SENTINEL"}}).to_string();
Expand All @@ -537,6 +542,8 @@ async fn dispatch_telemetry_is_correlated_and_content_free() {
assert_eq!(call(&adapter, "fractal_alert_get", &json!({})).await.unwrap_err()["code"], "invalid-arguments");
let text = logs.text();
assert_eq!(text.matches("ChatGPT tool call finished").count(), 4, "{text}");
assert_eq!(text.matches("fractal_chatgpt::call").count(), 4, "{text}");
assert!(!text.contains("st_chatgpt::call"), "{text}");
for field in ["tool=\"fractal_alerts_list\"", "outcome=\"success\"", "code=\"ok\"",
"outcome=\"error\"", "code=\"daemon-denied\"", "code=\"invalid-arguments\"",
"tool=\"unknown\"", "duration_ms=", id, &format!("response_bytes={expected_bytes}")] {
Expand All @@ -554,7 +561,7 @@ async fn dispatch_telemetry_records_decode_transport_and_cancelled_outcomes() {
.with_writer(logs.clone()).finish();
let _capture = tracing::subscriber::set_default(subscriber);
let dir = tempfile::tempdir().unwrap();
let socket = dir.path().join("st3-client.sock");
let socket = dir.path().join("fractal-client.sock");
let listener = tokio::net::UnixListener::bind(&socket).unwrap();
let entered = std::sync::Arc::new(tokio::sync::Notify::new());
let notify = entered.clone();
Expand Down
31 changes: 22 additions & 9 deletions docs/site/reference/mcp-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ order: 6

The Fractal MCP server connects an MCP client to the daemon's paired-client gateway. It exposes a closed set of tools for alerts, missions, agents, conversations, and explicitly scoped writes. It does not expose daemon control or give an assistant person authority.

The Rust package is `fractal-chatgpt`; its binary is `fractal-chatgpt` (`st-chatgpt` kept as an alias binary). The supported assistant principal is `assistant/chatgpt`, including when a generic MCP client uses the adapter. Pairing requires the configured owning person's explicit consent to disclose assistant reads to OpenAI. Do not use a person credential for this server.
The Rust package and binary are `fractal-chatgpt`. The supported assistant principal is `assistant/chatgpt`, including when a generic MCP client uses the adapter. Pairing requires the configured owning person's explicit consent to disclose assistant reads to OpenAI. Do not use a person credential for this server.

The [external-assistant contract](https://github.com/compoundingtech/fractal/blob/main/docs/st3/external-assistants.md) defines the authority and wire behavior. Remote access is a restricted dogfood path, not an authenticated public service.

Expand All @@ -19,16 +19,29 @@ Install and configure [Fractal](../getting-started/install.md) first. The daemon
Build the adapter from the repository root with Rust and the repository's build prerequisites:

```sh
cargo build --release -p fractal-chatgpt --bin st-chatgpt
cargo build --release -p fractal-chatgpt --bin fractal-chatgpt
```

The binary is `target/release/st-chatgpt`. The source package also provides the canonical `fractal-chatgpt` binary via `--bin fractal-chatgpt`; the examples here use `st-chatgpt`, which both installation methods provide. Alternatively, obtain the current Nix package:
The binary is `target/release/fractal-chatgpt`. Alternatively, obtain the current Nix package:

```sh
nix build github:compoundingtech/fractal#st-chatgpt
nix build github:compoundingtech/fractal#fractal-chatgpt
```

That binary is `result/bin/st-chatgpt`. Use a matching Fractal revision. The shell examples below assume `st-chatgpt` is on `PATH`; in the MCP client configuration, use the absolute path to the binary produced by your chosen installation method.
That binary is `result/bin/fractal-chatgpt`. Use a matching Fractal revision. The shell examples below assume `fractal-chatgpt` is on `PATH`; in the MCP client configuration, use the absolute path to the binary produced by your chosen installation method.

<!-- LIVE-MIGRATION BRIDGE arn:lmig:smalltalk:2026-11-01-fractal-chatgpt-alias — DELETE at contraction — https://github.com/compoundingtech/fractal/issues/3032 -->
The legacy executable `st-chatgpt` and Nix attribute `packages.st-chatgpt` remain bridged aliases for existing deployments. New installations use `fractal-chatgpt`.
<!-- LIVE-MIGRATION END arn:lmig:smalltalk:2026-11-01-fractal-chatgpt-alias -->

Call telemetry uses the `fractal_chatgpt::call` target and is disabled by default. Set
`RUST_LOG=off,fractal_chatgpt::call=info` to enable content-free call summaries on stderr.
<!-- LIVE-MIGRATION BRIDGE arn:lmig:smalltalk:2026-11-07-fractal-chatgpt-telemetry-target — DELETE at contraction — https://github.com/compoundingtech/fractal/issues/3032 -->
During a mixed-version rollout, set
`RUST_LOG=off,fractal_chatgpt::call=info,st_chatgpt::call=info` before upgrading.
The hard allowlist accepts both targets, but this release emits only `fractal_chatgpt::call`;
an old-target-only environment filter does not enable the renamed events.
<!-- LIVE-MIGRATION END arn:lmig:smalltalk:2026-11-07-fractal-chatgpt-telemetry-target -->

## Pair and prepare a grant

Expand Down Expand Up @@ -83,7 +96,7 @@ CLI pairing selects `caller-bound-allow`: reads within the sealed scopes, consen
Use stdio when the MCP client launches a local process:

```sh
st-chatgpt --transport stdio \
fractal-chatgpt --transport stdio \
--socket /PATH/TO/fractal-client.sock \
--credential-file /PATH/TO/mcp-private/credential
```
Expand All @@ -99,7 +112,7 @@ A generic MCP client configuration looks like this; adapt the outer configuratio
{
"mcpServers": {
"fractal": {
"command": "/PATH/TO/st-chatgpt",
"command": "/PATH/TO/fractal-chatgpt",
"args": [
"--transport", "stdio",
"--socket", "/PATH/TO/fractal-client.sock",
Expand All @@ -115,7 +128,7 @@ A generic MCP client configuration looks like this; adapt the outer configuratio
HTTP is the default transport. The default listen address is `127.0.0.1:8787`:

```sh
st-chatgpt --transport http --listen 127.0.0.1:8787 \
fractal-chatgpt --transport http --listen 127.0.0.1:8787 \
--socket /PATH/TO/fractal-client.sock \
--credential-file /PATH/TO/mcp-private/credential
```
Expand Down Expand Up @@ -150,7 +163,7 @@ The table summarizes the descriptions published by the adapter. A scope listed h
| `fractal_conversation_read` | `read.assistant.conversation` | Read | Read one bounded revision-pinned conversation content range. |
| `fractal_message_send` | `write.assistant.messages` | Write | Send one exact message to any existing agent; person recipients are refused. |

The corresponding `st_*` tool names are hidden, deprecated aliases for cached clients and are removed in `fractal-chatgpt` 0.2.0. Refresh client tool discovery and use `fractal_*`; aliases do not grant additional authority. This tool-name deprecation is separate from the `st-chatgpt` alias binary.
The corresponding `st_*` tool names are hidden, deprecated aliases for cached clients and are removed in `fractal-chatgpt` 0.2.0. Refresh client tool discovery and use `fractal_*`; aliases do not grant additional authority. This tool-name deprecation is separate from the bridged executable alias.

## Limits and grant lifecycle

Expand Down
12 changes: 6 additions & 6 deletions docs/st3/chatgpt-app.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# ChatGPT app: composition and install/use

Design for [#2172](https://github.com/compoundingtech/smalltalk/issues/2172), building on the assistant
principal and grants in [#2290](https://github.com/compoundingtech/smalltalk/pull/2290) and the assistant
routes in [#2541](https://github.com/compoundingtech/smalltalk/pull/2541). This is a standalone Fractal app.
Design for [#2172](https://github.com/compoundingtech/fractal/issues/2172), building on the assistant
principal and grants in [#2290](https://github.com/compoundingtech/fractal/pull/2290) and the assistant
routes in [#2541](https://github.com/compoundingtech/fractal/pull/2541). This is a standalone Fractal app.

## Composition

Expand All @@ -13,7 +13,7 @@ ChatGPT web (developer-mode app) ChatGPT desktop (local plugin)
│ │
MCP Streamable HTTP │
└───────────────┬─────────────────────┘
st-chatgpt adapter
fractal-chatgpt adapter
│ typed client-v0 assistant operations
paired Fractal gateway
│
Expand All @@ -24,7 +24,7 @@ ChatGPT web (developer-mode app) ChatGPT desktop (local plugin)
| --- | --- | --- |
| fractal daemon (`crates/st3`) | Principal, grants, scopes, owner approvals, fences and action receipts | Speak MCP or expose the control socket to ChatGPT |
| Typed client (`crates/st3-client`) | Client-v0 assistant operations | Widen authority |
| Adapter (`crates/st-chatgpt`) | Both MCP transports, closed tool list, annotations and pluggable authentication | Run arbitrary commands, expose `fractal.sock` or return credentials |
| Adapter (`crates/fractal-chatgpt`) | Both MCP transports, closed tool list, annotations and pluggable authentication | Run arbitrary commands, expose `fractal.sock` or return credentials |
| ChatGPT app or plugin record | Connection to the adapter | Hold Fractal credentials |
| Downstream deployment | Adapter and tunnel services for one installation | Fork the authority model or tool list |

Expand Down Expand Up @@ -75,7 +75,7 @@ For an example owner, `person/ada`:
2. For web, create a Secure MCP Tunnel in the owner's OpenAI Platform organization, associate it with the
ChatGPT workspace, and run its tunnel client against the adapter's HTTP endpoint. The owner creates
the developer-mode ChatGPT app record and selects the tunnel.
3. For desktop, configure the local plugin to launch `st-chatgpt` with the stdio transport in a Work/Codex
3. For desktop, configure the local plugin to launch `fractal-chatgpt` with the stdio transport in a Work/Codex
thread. It uses the same authority model and tool list as HTTP.
4. Ask what needs attention, read the bounded alert or transcript, or send a message to an allowlisted
agent. For a person-ask answer, request the exact option, approve the action in Fractal, then execute it once.
Expand Down
6 changes: 3 additions & 3 deletions docs/st3/client-v0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1883,9 +1883,9 @@ frontend-auth boundary and does not alter tool arguments or daemon grants. No ge
runner, daemon-control socket, or terminal scraping is exposed. `GET /mcp` is 405; unknown
paths return an empty 404.

The flake's `packages.st-chatgpt` builds only the adapter executable with the shared locked
Cargo dependencies. Run `nix build .#st-chatgpt` from a checkout, or
`nix build github:compoundingtech/fractal/<commit>#st-chatgpt` at a published commit.
The flake's `packages.fractal-chatgpt` builds only the adapter executable with the shared locked
Cargo dependencies. Run `nix build .#fractal-chatgpt` from a checkout, or
`nix build github:compoundingtech/fractal/<commit>#fractal-chatgpt` at a published commit.
The shared `checks.st3` suite includes the adapter's Rust tests.

Rerun the scratch exercise (no live daemon or ChatGPT application is touched):
Expand Down
11 changes: 11 additions & 0 deletions release-notes/fractal-chatgpt-surface-rename.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": 1,
"summary": "Use fractal-chatgpt for MCP server identity, adapter documentation, and call telemetry.",
"replay": {"status": "none", "detail": "No stored history changes."},
"schema": {"status": "none", "detail": "No database or daemon wire schema changes."},
"rules": {"status": "none", "detail": "No coordination rules change."},
"compatibility": {"status": "changed", "detail": "MCP serverInfo and CLI help identify fractal-chatgpt. Call events emit only fractal_chatgpt::call; the hard allowlist temporarily accepts st_chatgpt::call too. The legacy st-chatgpt executable remains a bridged alias. Cached tool aliases and paired-socket compatibility are unchanged."},
"downtime": {"status": "none", "detail": "No daemon downtime required."},
"manual": {"status": "changed", "detail": "Before upgrading, configure mixed-version deployments with RUST_LOG=off,fractal_chatgpt::call=info,st_chatgpt::call=info. An old-target-only filter does not enable new-target events. New installations use packages.fractal-chatgpt and bin/fractal-chatgpt."},
"recovery": {"status": "none", "detail": "The dual-target environment filter supports the previous adapter binary on rollback."}
}
Loading