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
16 changes: 11 additions & 5 deletions docs/source/development/mcp_release.rst
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,9 @@ The same helper can be invoked outside Actions after SimplySign is connected:

The macOS job requires a Developer ID Application certificate, not a Developer
ID Installer certificate, because the shipped objects are an application and
disk image rather than a flat installer package. It also requires an App Store
Connect API key authorized for the Apple notary service. Store:
disk image rather than a flat installer package. It also requires a team App
Store Connect API key authorized for the Apple notary service; Apple does not
permit individual App Store Connect API keys to use ``notarytool``. Store:

.. code-block:: text

Expand All @@ -221,9 +222,12 @@ key; the key ID and issuer ID are the corresponding App Store Connect values.
The workflow imports the certificate into an ephemeral keychain, signs the app
with hardened runtime and a secure timestamp, verifies that signature, builds
and signs the DMG, and submits the exact DMG with ``notarytool --wait``. It
requires the returned status to be ``Accepted``, staples and validates the
ticket, and runs a Gatekeeper assessment before upload. Temporary keychains,
certificate files, and API-key files are removed by the trust helper.
requires the returned status to be ``Accepted``, retrieves the authoritative
log for that exact submission with the same credentials, emits the complete
log so Apple warnings remain visible, and independently requires the log status
to be ``Accepted``. Only then does it staple and validate the ticket and run a
Gatekeeper assessment before upload. Temporary keychains, certificate files,
notarization logs, and API-key files are removed by the trust helper.

The DMG is the trust acceptance boundary because it is the exact outermost
file distributed to users. Apple directs custom distributions to sign nested
Expand Down Expand Up @@ -253,6 +257,8 @@ Publisher setup should follow the current primary platform guidance:
<https://developer.apple.com/developer-id/>`_.
* `Apple custom notarization workflow
<https://developer.apple.com/documentation/security/customizing-the-notarization-workflow>`_.
* `Apple App Store Connect API key types
<https://developer.apple.com/documentation/appstoreconnectapi/creating-api-keys-for-app-store-connect-api>`_.
* `Apple packaging guidance for nested distributions
<https://developer.apple.com/documentation/xcode/packaging-mac-software-for-distribution>`_.
* `Apple Gatekeeper assessment with ``spctl``
Expand Down
43 changes: 22 additions & 21 deletions docs/source/user_guide/mcp_clients.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ Local installation
------------------

For normal desktop onboarding, download the native OpenHCS installer for
Windows or macOS and leave **Connect OpenHCS to Codex and installed local AI
Windows or macOS and leave **Connect OpenHCS to ChatGPT, Codex, and local AI
agent apps** checked. Setup installs the GUI and MCP runtime together, publishes
one update-stable launcher, and registers that launcher with Codex plus other
detected supported local clients. Restart the client after setup, accept its
normal first-use trust prompt if shown, and ask it to use OpenHCS. This local
Codex setup does not use ChatGPT Developer Mode.
one update-stable launcher, and registers that launcher with ChatGPT desktop,
Codex, and other detected supported local clients. Restart the clients after
setup, accept their normal first-use trust prompt if shown, and ask them to use
OpenHCS. This local desktop setup does not use ChatGPT Developer Mode.

The installer preserves unrelated MCP servers and replaces only the local entry
named ``openhcs``. Re-running Setup updates the private OpenHCS environment
Expand Down Expand Up @@ -137,12 +137,13 @@ Codex CLI fallback is a single local-server registration command:
--env OPENHCS_AGENT_WRITE_ROOTS=/path/to/openhcs-outputs \
-- uvx --from 'openhcs[gui,mcp]' openhcs-mcp

The Codex app, CLI, and IDE extension share the MCP configuration for the same
host. In the current unified ChatGPT desktop app, choose the distinct Codex
view; that view retains the local Codex workflow and configuration. Restart the
client after adding or installing the server. Chat and Work do not read this
local configuration or directly start a local stdio MCP server; use a remote
HTTPS app or Secure MCP Tunnel for those views as described below.
ChatGPT desktop, the Codex app and CLI, and the Codex IDE extension share the
MCP configuration for the same host. Restart ChatGPT desktop or Codex after
adding or installing the server. In ChatGPT desktop, open **Settings**, then
**MCP servers**, to inspect the connection; after restarting, ``/mcp`` shows
the connected servers. The ChatGPT web client does not read this local
configuration or directly start a local stdio MCP server; use a remote HTTPS
app or Secure MCP Tunnel there as described below.

The native installer performs this local registration automatically. The CLI
command above remains the manual/package-manager fallback and is not required
Expand Down Expand Up @@ -197,19 +198,19 @@ local bridge.
Browser clients
---------------

ChatGPT Chat and Work cannot directly install Python packages, launch
The ChatGPT web client cannot directly install Python packages, launch
``openhcs-mcp`` over stdio, or start the native OpenHCS UI on the user's
computer. Those views connect to remote MCP servers. A separately deployed
HTTPS MCP service can operate on server-side workspaces; an OpenAI Secure MCP
Tunnel can bridge an eligible ChatGPT workspace to a private deployment without
exposing it publicly. Neither route is created merely by writing Codex's local
configuration. The Codex view in the unified desktop app remains the local
stdio route described above.
computer. It connects to remote MCP servers. A separately deployed HTTPS MCP
service can operate on server-side workspaces; an OpenAI Secure MCP Tunnel can
bridge an eligible ChatGPT workspace to a private deployment without exposing
it publicly. Neither route is created merely by writing the shared local
ChatGPT desktop and Codex configuration. ChatGPT desktop remains a supported
local stdio route as described above.

The hosted service exposes a smaller, read-only discovery surface rather than
your local OpenHCS installation. It requires OAuth and an isolated server-side
workspace. Add the administrator-provided HTTPS MCP URL to ChatGPT, or follow
the workspace administrator's Secure MCP Tunnel procedure. ChatGPT plan,
workspace. Add the administrator-provided HTTPS MCP URL to ChatGPT web, or
follow the workspace administrator's Secure MCP Tunnel procedure. ChatGPT plan,
workspace, Developer Mode, and approval requirements are controlled by OpenAI
and may differ from local Codex onboarding. Do not expose ``openhcs-mcp`` or
and may differ from local desktop onboarding. Do not expose ``openhcs-mcp`` or
``openhcs-mcp-http`` directly from a personal machine.
13 changes: 8 additions & 5 deletions openhcs/mcp/client_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,15 @@ def diagnostic_config_path(


class CodexClientRegistrationTarget(McpClientRegistrationTarget):
"""Codex app, CLI, and IDE shared TOML configuration."""
"""ChatGPT desktop and Codex shared TOML configuration."""

target_id = "codex"
display_name = "OpenAI Codex"
display_name = "ChatGPT desktop and OpenAI Codex"
executable_candidates = ("codex",)

@classmethod
def config_path(cls, environment: ClientRegistrationEnvironment) -> Path:
"""Return the shared Codex configuration path."""
"""Return the shared ChatGPT desktop and Codex configuration path."""
configured_home = environment.environ.get("CODEX_HOME")
codex_home = (
Path(configured_home).expanduser()
Expand Down Expand Up @@ -637,7 +637,7 @@ def _update_codex_toml(
status=ClientRegistrationStatus.UNCHANGED,
config_path=os.fspath(path),
backup_path=None,
message="OpenHCS is already registered with Codex.",
message="OpenHCS is already registered with ChatGPT desktop and Codex.",
)

server = (
Expand All @@ -658,7 +658,10 @@ def _update_codex_toml(
),
config_path=os.fspath(path),
backup_path=backup_path,
message="Registered OpenHCS in the shared Codex MCP configuration.",
message=(
"Registered OpenHCS in the shared ChatGPT desktop and Codex "
"MCP configuration."
),
)


Expand Down
33 changes: 16 additions & 17 deletions packaging/installers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ and macOS users open one disk image. Installation stays inside a small native
window:

- Windows presents Welcome, installation-folder, progress, and Finish pages.
Its checked agent option connects the Codex app/CLI/IDE and detected supported
local clients. The final page reports whether those connections succeeded
and can launch OpenHCS immediately.
Its checked agent option connects ChatGPT desktop, Codex app/CLI/IDE, and
detected supported local clients. The final page reports whether those
connections succeeded and can launch OpenHCS immediately.
- macOS presents Welcome, progress, and Finish pages in
``OpenHCS Installer.app``. Its equivalent checked agent option is shown on the
Welcome page, and the final page can launch OpenHCS immediately.
Expand All @@ -61,20 +61,19 @@ individual OpenHCS dependencies. Advanced output remains available through the
durable installer log when troubleshooting is needed.

After a successful agent connection, restart the local client and ask it to use
OpenHCS. The Codex app, Codex CLI, and the Codex IDE extension share one
registration. In the current unified ChatGPT desktop app, this is the distinct
Codex view; select Codex before asking it to use OpenHCS. Claude Desktop,
Cursor, Gemini CLI, and Windsurf are configured when detected; VS Code is
registered through its supported command-line interface when available. A
client may still show its normal first-use trust or tool-approval prompt. This
local Codex setup does not use ChatGPT Developer Mode.

The Chat and Work views in ChatGPT do not directly start this local stdio
process or read Codex's local configuration. Those views require a remote HTTPS
MCP app or an OpenAI Secure MCP Tunnel, with availability and approval
controlled by the user's ChatGPT plan and workspace. The official MCP Registry
record provides distribution discovery and verification, not a
browser-to-local bridge.
OpenHCS. ChatGPT desktop, the Codex app and CLI, and the Codex IDE extension
share one registration. In ChatGPT desktop, use **Settings > MCP servers** to
inspect the connection and ``/mcp`` after restarting to list connected servers.
Claude Desktop, Cursor, Gemini CLI, and Windsurf are configured when detected;
VS Code is registered through its supported command-line interface when
available. A client may still show its normal first-use trust or tool-approval
prompt. This local desktop setup does not use ChatGPT Developer Mode.

The ChatGPT web client does not directly start this local stdio process or read
the shared local configuration. It requires a remote HTTPS MCP app or an OpenAI
Secure MCP Tunnel, with availability and approval controlled by the user's
ChatGPT plan and workspace. The official MCP Registry record provides
distribution discovery and verification, not a browser-to-local bridge.

## Source validation

Expand Down
6 changes: 3 additions & 3 deletions packaging/installers/macos/OpenHCSInstaller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private final class InstallerController: NSObject, NSApplicationDelegate,
private let progressIndicator = NSProgressIndicator()
private let connectAgentsCheckbox = NSButton(
checkboxWithTitle:
"Connect OpenHCS to Codex and installed local AI agent apps",
"Connect OpenHCS to ChatGPT, Codex, and local AI agent apps",
target: nil,
action: nil
)
Expand Down Expand Up @@ -345,10 +345,10 @@ private final class InstallerController: NSObject, NSApplicationDelegate,
if installerStateValue(named: "agent-registration-status") == "connected" {
let connectedClients =
installerStateValue(named: "agent-registration-summary")
?? "Codex and detected local agent apps"
?? "ChatGPT desktop, Codex, and detected local agent apps"
detailLabel.stringValue =
"OpenHCS is connected to \(connectedClients). "
+ "In ChatGPT desktop, choose Codex. Restart other listed apps, "
+ "Restart ChatGPT desktop, Codex, and other listed apps, "
+ "then ask them to use OpenHCS."
} else if installerStateValue(
named: "agent-registration-status"
Expand Down
35 changes: 35 additions & 0 deletions packaging/installers/macos/Sign-Installer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ sign_dmg_and_notarize() {

local notary_key_path="$temporary_directory/AuthKey.p8"
local notary_result_path="$temporary_directory/notary-result.json"
local notary_log_path="$temporary_directory/notary-log.json"
if ! printf '%s' "$OPENHCS_MACOS_NOTARY_KEY_BASE64" |
/usr/bin/base64 -D >"$notary_key_path"; then
printf '%s\n' \
Expand Down Expand Up @@ -195,6 +196,40 @@ sign_dmg_and_notarize() {
exit 1
fi

local notary_submission_id
notary_submission_id=$(
/usr/bin/plutil \
-extract id raw \
-o - \
"$notary_result_path"
)
if [[ -z "$notary_submission_id" ]]; then
printf 'Apple notarization returned no submission identifier.\n' >&2
exit 1
fi
if ! /usr/bin/xcrun notarytool log "$notary_submission_id" \
--key "$notary_key_path" \
--key-id "$OPENHCS_MACOS_NOTARY_KEY_ID" \
--issuer "$OPENHCS_MACOS_NOTARY_ISSUER_ID" \
"$notary_log_path"; then
printf 'Could not retrieve the Apple notarization log.\n' >&2
exit 1
fi
/bin/cat "$notary_log_path"

local notary_log_status
notary_log_status=$(
/usr/bin/plutil \
-extract status raw \
-o - \
"$notary_log_path"
)
if [[ "$notary_log_status" != Accepted ]]; then
printf 'Apple notarization log was not accepted: %s\n' \
"$notary_log_status" >&2
exit 1
fi

/usr/bin/xcrun stapler staple "$dmg_path"
/usr/bin/xcrun stapler validate "$dmg_path"
verify_timestamped_signature "$dmg_path"
Expand Down
10 changes: 6 additions & 4 deletions packaging/installers/windows/Install-OpenHCS.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1059,7 +1059,7 @@ function Show-InstallerWindow {

$agentConnectionCheck = New-Object Windows.Forms.CheckBox
$agentConnectionCheck.Text = (
"Connect OpenHCS to Codex and installed local AI agent apps"
"Connect OpenHCS to ChatGPT, Codex, and local AI agent apps"
)
$agentConnectionCheck.Checked = $true
$agentConnectionCheck.Location = New-Object Drawing.Point(31, 201)
Expand Down Expand Up @@ -1470,7 +1470,9 @@ function Show-InstallerWindow {
).Trim()
}
if ($registrationStatus -eq "connected") {
$connectedClients = "Codex and detected local agent apps"
$connectedClients = (
"ChatGPT desktop, Codex, and detected local agent apps"
)
$registrationReportPath = [IO.Path]::Combine(
$script:ActiveInstallRoot, "agent-registration.json"
)
Expand Down Expand Up @@ -1498,8 +1500,8 @@ function Show-InstallerWindow {
}
$completionMessage = (
"$($Contract.ProductName) is connected to $connectedClients. " +
"In ChatGPT desktop, choose Codex. Restart other listed " +
"apps, then ask them to use OpenHCS."
"Restart ChatGPT desktop, Codex, and other listed apps, " +
"then ask them to use OpenHCS."
)
}
else {
Expand Down
13 changes: 13 additions & 0 deletions tests/installer/test_installer_signing.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ def test_macos_release_uses_developer_id_notarytool_and_stapling() -> None:
verify = dmg.index('verify_timestamped_signature "$dmg_path"')
submit = dmg.index("/usr/bin/xcrun notarytool submit")
accepted = dmg.index('if [[ "$notary_status" != Accepted ]]')
log = dmg.index('/usr/bin/xcrun notarytool log "$notary_submission_id"')
log_accepted = dmg.index('if [[ "$notary_log_status" != Accepted ]]')
staple = dmg.index("/usr/bin/xcrun stapler staple")
validate = dmg.index("/usr/bin/xcrun stapler validate")
final_signature = dmg.rindex('verify_timestamped_signature "$dmg_path"')
Expand All @@ -128,6 +130,8 @@ def test_macos_release_uses_developer_id_notarytool_and_stapling() -> None:
< verify
< submit
< accepted
< log
< log_accepted
< staple
< validate
< final_signature
Expand All @@ -137,6 +141,15 @@ def test_macos_release_uses_developer_id_notarytool_and_stapling() -> None:
assert dmg.count('/usr/bin/hdiutil verify "$dmg_path"') == 2
assert "--wait" in dmg
assert "--output-format json" in dmg
assert "-extract id raw" in dmg
assert '"$notary_log_path"' in dmg
log_block = dmg[log:log_accepted]
for credential_argument in (
'--key "$notary_key_path"',
'--key-id "$OPENHCS_MACOS_NOTARY_KEY_ID"',
'--issuer "$OPENHCS_MACOS_NOTARY_ISSUER_ID"',
):
assert credential_argument in log_block


def test_tag_workflow_cannot_upload_unsigned_native_artifacts() -> None:
Expand Down
5 changes: 2 additions & 3 deletions tests/installer/test_macos_simple_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,12 @@ def test_macos_installer_registers_agent_clients_through_stable_launcher() -> No
assert '"$registration_ok" != true' in source

assert "connectAgentsCheckbox" in app_source
assert "Connect OpenHCS to Codex and installed local AI agent apps" in app_source
assert "ChatGPT/Codex" not in app_source
assert "Connect OpenHCS to ChatGPT, Codex, and local AI agent apps" in app_source
assert "connectAgentsCheckbox.state = .on" in app_source
assert 'environment["OPENHCS_INSTALLER_REGISTER_MCP_CLIENTS"]' in app_source
assert 'installerStateValue(named: "agent-registration-status")' in app_source
assert 'installerStateValue(named: "agent-registration-summary")' in app_source
assert "In ChatGPT desktop, choose Codex" in app_source
assert "Restart ChatGPT desktop, Codex, and other listed apps" in app_source

macos_smoke = workflow[
workflow.index(
Expand Down
7 changes: 2 additions & 5 deletions tests/installer/test_windows_simple_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,7 @@ def test_windows_wizard_owns_liveness_failure_and_optional_launch_ui() -> None:
assert '$openLogButton.Text = "Open log"' in source
assert '$launchCheck.Text = "Launch $($Contract.ProductName) after setup"' in source
assert "$launchCheck.Checked = $true" in source
assert (
'"Connect OpenHCS to Codex and installed local AI agent apps"' in source
)
assert "ChatGPT/Codex" not in source
assert '"Connect OpenHCS to ChatGPT, Codex, and local AI agent apps"' in source
assert "$agentConnectionCheck.Checked = $true" in source
assert "Get-DesktopShortcutPath $Contract" in source
assert "Start-Process -FilePath (Get-DesktopShortcutPath $Contract)" in source
Expand Down Expand Up @@ -295,7 +292,7 @@ def test_windows_installer_registers_agent_clients_through_stable_launcher() ->
assert "$registrationReport.results" in source
assert "[string]$_.display_name" in source
assert "Register-InstalledMcpClients" in source
assert "In ChatGPT desktop, choose Codex" in source
assert "Restart ChatGPT desktop, Codex, and other listed apps" in source
assert "$exitCode -ne 0" in source
assert "$report.ok -ne $true" in source
assert source.index("Publish-LaunchAdapterAndShortcut `") < source.index(
Expand Down
5 changes: 4 additions & 1 deletion tests/unit/agent/test_mcp_client_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ def test_registered_client_targets_are_the_client_semantic_authority() -> None:
"windsurf": WindsurfClientRegistrationTarget,
"vscode": VsCodeClientRegistrationTarget,
}
assert CodexClientRegistrationTarget.display_name == "OpenAI Codex"
assert (
CodexClientRegistrationTarget.display_name
== "ChatGPT desktop and OpenAI Codex"
)


def test_codex_registration_preserves_unrelated_toml_and_creates_backup(
Expand Down
Loading
Loading