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
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Codex plugin
on: [push, pull_request]
permissions:
contents: read
jobs:
verify:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
- run: npm install --global npm@11.6.2
# `npm ci --ignore-scripts` once the pinned devtools is on the registry. Until then
# the pinned version is built from the devtools repository at this ref and checked
# against the lockfile integrity. Remove DEVTOOLS_SOURCE_REF after publication.
- run: node scripts/install-devtools.cjs
env:
DEVTOOLS_SOURCE_REF: codex/extract-codex-plugin
- run: npm run release:check
41 changes: 41 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Release Codex plugin
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
publish:
description: Publish the checked npm artifact
type: boolean
default: false
permissions:
contents: read
id-token: write
concurrency:
group: codex-plugin-release
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-latest
environment: npm
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
registry-url: https://registry.npmjs.org
- run: npm install --global npm@11.6.2
# A release always builds from the published devtools archive named in the lockfile.
- run: npm ci --ignore-scripts
- name: Check release tag
if: github.ref_type == 'tag'
run: node -e 'if(process.env.GITHUB_REF_NAME !== `v${require("./package.json").version}`) process.exit(1)'
- run: npm run release:check
- uses: actions/upload-artifact@v4
with:
name: mainsequence-codex-plugin-release
include-hidden-files: true
path: artifacts/*.tgz
- name: Publish the exact verified archive using npm trusted publishing
if: github.ref_type == 'tag' || inputs.publish
run: npm publish "./artifacts/dev-mainsequence-codex-plugin-$(node -p 'require("./package.json").version').tgz" --ignore-scripts --access public --provenance
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
node_modules/
/build/
/codex/
/artifacts/
*.tgz
.env

# Mirrored plugin artifact at the package root, written by scripts/build-codex.cjs for npm pack.
/dist/
/.codex-plugin/
/hooks/
/skills/
/assets/
/build-info.json
/plugin.json
143 changes: 142 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,144 @@
# MainSequence for Codex

The MainSequence plugin for Codex. The first release lands in the next pull request.
The `mainsequence` plugin registers the `ms` slash-menu entry in Codex and executes its
commands with a trusted `UserPromptSubmit` hook before inference: login, session status,
repositories, project status, dependency sync, SDK updates and more, with readable results
and no model call. Its `mainsequence-mcp` skill lets Codex discover MainSequence project
MCPs in conversation, remember a selection and use that project's tools through a bundled
MCP client.

Version **0.3.0** is the first release from this repository. Until devtools 0.2.8 the plugin
was built and published from
[mainsequence-devtools](https://github.com/mainsequence-sdk/mainsequence-devtools) under the
name `mainsequence-devtools` ([ADR 0003 there](https://github.com/mainsequence-sdk/mainsequence-devtools/blob/main/docs/adr/0003-codex-plugin-extraction.md)).
The shared implementation is still that package; this repository owns the hook adapter, the
presentation layer, the manifests, skills and icons, the host checks and the releases.

## Install

The marketplace stays where people added it, in the devtools repository:

```sh
codex plugin marketplace add mainsequence-sdk/mainsequence-devtools
codex plugin add mainsequence@mainsequence
```

Codex installs the published npm package `@dev-mainsequence/codex-plugin` without running
lifecycle scripts; the archive already contains the compiled runner and the pinned devtools
package. Node 22 or newer is required. Review and trust the plugin's hook before use
(`/hooks` in the CLI, or the hook prompt in the desktop app): installation does not trust
hooks, and an untrusted hook cannot intercept `/ms`. Then start a new chat, or toggle
MainSequence off and on in the desktop Plugins settings to hot-reload an existing chat.

### Upgrading from `mainsequence-devtools`

The plugin was renamed, and Codex identifies a plugin by its marketplace entry name, so the
installed `mainsequence-devtools@mainsequence` is not updated in place. Refresh the
marketplace, install the new entry, trust its hook, then remove the old plugin so that only
one hook answers `/ms`:

```sh
codex plugin marketplace upgrade mainsequence
codex plugin add mainsequence@mainsequence
codex plugin remove mainsequence-devtools@mainsequence
```

Your MainSequence login (the shared machine session) and saved MCP connections
(`mcp-connections.json` beside the CLI configuration) are not stored by the plugin, so
nothing needs migrating. The exact behaviour of the installed host during this change is
recorded in [the verification document](docs/codex-verification.md).

### A local build

```sh
npm run build
codex plugin marketplace add /absolute/path/to/mainsequence-codex-plugin/artifacts/local-marketplace
codex plugin add mainsequence@mainsequence-local
```

The local catalog loads the built artifact in `codex/`; the installed plugin runs from
Codex's cache. `npm run pack:codex` also writes a standalone archive of that artifact.

## Use

In Codex desktop, type `/ms` and **select `ms` from the slash menu**, then append the
arguments and submit:

```text
/ms --help
/ms login --backend https://api.main-sequence.app
/ms status
/ms sync
```

Selection inserts a native skill mention. The hook accepts that mention only when it
references this plugin's own `skills/ms/SKILL.md`, parses the single-line arguments as data
without any shell, and blocks the turn with a readable result on success and failure, so a
handled command never reaches the model. `/ms --help --all` lists every command and
`/ms COMMAND --help` its options; `--json` exports the structured result. Ordinary prompts
pass through. Codex CLI rejects custom `/ms` input before submission; use `ms-devtools` in
a terminal there (see below).

For project MCPs, ask in conversation: "Which internal MCPs are available?", "Add
metatable-mcp", "Use it to list my tables." See
[conversational project MCP connections](docs/conversational-mcp.md) and
[ADR 0002](docs/adr/0002-conversational-project-mcp-client.md).

## Architecture and development

Shared functionality comes from the exact `@dev-mainsequence/devtools` package API. The
build (`scripts/build-codex.cjs`) compiles `src/` to `build/`, then writes the installable
artifact to `codex/`: the manifest, hooks, skills and icons from `manifests/codex`, the
runner and hook adapter with every reachable devtools `dist` module vendored into
`dist/vendor/devtools`, and `build-info.json` with the pinned devtools version and
registry integrity. Codex's npm plugin source reads the manifest from the package root, so
the build mirrors `.codex-plugin`, `hooks`, `skills`, `assets`, `build-info.json` and the
bundled `dist` there for `npm pack`; those mirrors are ignored by Git. An installed plugin
needs neither this checkout, nor `node_modules`, nor an npm download at execution time.

`src/runner.ts` is the plugin's entry point: `dist/runner.js --codex-hook` serves the
hook, and any other invocation is the plain `ms-devtools` terminal CLI, so this package's
`bin` keeps working in a shell (`npx -p @dev-mainsequence/codex-plugin ms-devtools --help`).
`src/hosts/codex.ts` recognises `/ms` and the linked skill mention and dispatches through
devtools' exported parser and tokenizer; `src/hosts/codexPresentation.ts` renders help and
results. `tests/dependency-boundary.test.cjs` forbids private devtools imports.

### Dependencies

`npm ci --ignore-scripts` installs the pinned devtools from the registry with the integrity
recorded in `package-lock.json`. While that version is not published yet, run

```sh
node scripts/install-devtools.cjs /path/to/dev-mainsequence-devtools-0.3.0.tgz
```

with the archive from `npm pack` of the devtools release branch, or without an argument to
build it from the devtools repository at `DEVTOOLS_SOURCE_REF` (default `v<pinned version>`).
Either way the archive must match the lockfile integrity, and the registry pin is not
changed (`scripts/use-local-devtools.cjs`). CI does the same until the version is
published. After publication, confirm that `npm view @dev-mainsequence/devtools@0.3.0
dist.integrity` equals the lockfile entry before tagging a release; if the registry archive
differs, update the lockfile and rebuild so `build-info.json` records the published
integrity.

### Verification and release

```sh
npm run release:check
```

type-checks, builds, runs the Node tests (hook, presentation, packaging and dependency
boundary), checks release metadata, packs the standalone artifact and the npm archive, and
verifies the npm archive as Codex installs it. With a built plugin installed and trusted in
a Codex host, `npm run verify:codex` runs real app-server turns through a counted offline
model fixture, and `npm run verify:codex:reload` checks the existing-chat reload behaviour
in a temporary Codex home. Set `MAINSEQUENCE_CODEX_BINARY` when the Codex CLI is not on
`PATH`, `CODEX_HOME` to check an isolated home, and `MAINSEQUENCE_CODEX_PLUGIN_ID` for a
plugin installed from another marketplace. Results are recorded in
[docs/codex-verification.md](docs/codex-verification.md).

A `v<version>` tag runs the same checks from the published devtools archive and publishes
the verified npm archive with provenance through npm trusted publishing (repository
`mainsequence-sdk/mainsequence-codex-plugin`, workflow `release.yml`, environment `npm`).
Published versions are immutable. After publishing, point the marketplace entry in the
devtools repository at the new version.
59 changes: 59 additions & 0 deletions docs/adr/0002-conversational-project-mcp-client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# ADR 0002: Conversational project MCP connections in the Codex plugin

Date: 2026-10-09. Moved from mainsequence-devtools on 2026-10-21 with the Codex plugin; "this repository" below refers to devtools, where the MCP client (the `mcp-*` actions and the connection store) still lives. The skill, hook and bundled runner that expose it in Codex are released from this repository.

Status: Accepted for implementation by the owner's instruction, "OK then extend the codex plugin", following the explicit proposal to use a bundled client instead of native Codex MCP registration.

## Decision

Extend this repository's shared dispatcher and bundled runner with discovery, add, list, remove, tool-catalog and tool-call actions. Package a normally discoverable mainsequence-mcp skill alongside the explicit-only ms developer-command skill. A person can ask which project MCPs exist, select one, use its tools in the same conversation, and use the saved connection in later conversations. They do not invoke /ms or edit Codex configuration for these actions.

The plugin is an MCP client. Codex runs its bundled helper through its existing execution capability. Each project remains a direct remote MCP endpoint; the platform directory is not a proxy for its tools. No new service, background daemon, application deployment, backend endpoint, credential namespace, or native host modification is introduced.

The distinction from native registration is part of the contract: a saved project does not appear as its own native Codex MCP server or model tool namespace. The helper discovers the live schemas and invokes the selected tool through MCP. The plugin must already be installed and its conversational skill loaded by the host. Distributing an updated plugin is separate from adding a project connection.

## Authentication decision and evidence

Reuse the existing developer machine session and authenticated GET /api/v1/resource-releases/{uid}/exchange-launch/. For a FastAPI release this returns mode=token, release_kind=fastapi, an opaque short-lived release credential, and rpc_url. The current project gateway accepts these release credentials on POST /mcp under current User, release and Environment permissions. Resource-bound browser OAuth remains the independent direct-client login mechanism; this client neither modifies it nor copies a central OAuth token to a project.

This resolves the implementation question using an existing mechanism: an already signed-in developer needs no new browser consent per project and no new backend authorization handoff. A central MCP connection in Codex alone is not a machine session. When that session is missing or expired beyond renewal, the existing hosted developer login remains necessary; the plugin cannot promise first-time sign-in entirely within chat.

The inspected platform contract is owned by platform ADR-0050 sections 4 and 6. Its current text explicitly permits existing release bearer credentials at project MCP endpoints, and its gateway tests cover that path. This ADR records the decision to consume that existing contract so it can be evaluated independently of implementation. No platform ADR amendment or backend behavior change is required by this implementation. No agent-specific approval/exclusion list is introduced.

The platform credential is sent only to the selected backend. The release credential is sent only to the freshly resolved canonical project endpoint, after matching the exchange's RPC origin. It is held in memory for that invocation, redacted from results, and never added to connection files, tool arguments or command lines. Existing machine-session rotation, OS-store behavior and explicit process-identity precedence remain authoritative.

## Discovery and connection lifecycle

Discovery initializes central POST /mcp and calls resource_release.mcp_endpoints with search, limit and offset. It returns the existing directory projection; there is no copied application tool catalog.

Add selects a release UID, rereads the authenticated release detail, validates its current connection, exchanges a release credential, initializes the project and enumerates its paginated tools before saving. Repeated add replaces only the same backend/UID record. Duplicate display names require selecting a UID. Arbitrary caller-provided project URLs are not accepted.

Saved records contain only backend, release UID, display name, and the canonical MCP connection metadata (URL, transport, resource metadata URL, Environment UID, active revision UID). Store them in mcp-connections.json next to the existing CLI configuration:

- macOS: ~/Library/Application Support/MainSequenceCLI/
- Linux: ~/.config/mainsequence/
- Windows: %APPDATA%/MainSequenceCLI/ (connection metadata only; native saved authentication remains unsupported by the existing Node store).

Writes use a private file, a lock, and atomic replacement. Corrupt records are preserved and reported. Library/test hosts may supply mcpConnectionsPath without changing the platform credential store.

List is local and does not promise availability. Tools/call require a saved record, reread current release access and MCP capability, and reject an endpoint change until the person adds that release again. A new active revision on the same endpoint uses its current catalog. Remove forgets only the selected record; it does not log out or revoke credentials.

## Transport and operation semantics

Use only the platform's stateless Streamable HTTP profile, MCP 2025-11-25, including initialize, notifications/initialized, tools/list and tools/call. Support JSON and POST SSE results, bounded bodies, pagination, request timeouts and cancellation. Do not add deprecated HTTP+SSE endpoint discovery, protocol downgrade, stateful sessions, subscriptions, tasks, sampling, elicitation, native Codex registration or reload fallbacks.

All server-advertised ordinary tools can be selected, including writes. Tool annotations inform the harness; they do not constitute another permission system. The server validates tool arguments and operation permissions.

Project requests do not follow redirects and are never automatically replayed. Once a tools/call request starts, failure or cancellation reports potentially partial/uncertain effects. MCP isError results remain available as error data with known authentication credentials redacted.

## Classification and verification

A: Amend ADR 0001 to distinguish its original deterministic developer path from the newly approved conversational MCP client.

B: This ADR owns the new local connection lifecycle and host limitation.

C: This is an owner-requested feature, not a defect or code-only fix; no defect issue is opened.

D: Update README and the conversational MCP guide in the same change.

Verification uses separate loopback platform and project servers, synthetic credentials and temporary connection files. Cover discovery through the actual tool name, same-session use, persistence across client instances/processes, duplicate names, backend isolation, private renewal, revocation, endpoint changes, pagination, JSON/SSE, bounded transport failures, no write replay, cancellation, redaction and packaged runner execution. No live project operation or OpenAI inference is required. Real endpoint interoperability and automatic model selection remain deployment validation; fixture tests must not be described as live desktop end-to-end proof.
Loading
Loading