Lock agent-plugin strategy: Ghost as trust runtime + HTTP/MCP surface - #371
Conversation
Position Ghost as the trust runtime AI agents plug into (propose → human approve → execute → verify → audit). Add @ghost/core agent tool catalog, /api/agent HTTP surface (session or API key; no self-approve), stdio MCP bridge, and docs/marketing lock-in. Co-authored-by: Muhammad Rafiq <mohabbis@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 19d3b3fb40
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const session = await auth(); | ||
| if (!session?.user?.orgId || !session.user.id) { | ||
| return { ok: false, status: 401, error: "unauthorized" }; | ||
| } | ||
| return { | ||
| ok: true, | ||
| principal: { orgId: session.user.orgId, userId: session.user.id, via: "session" }, |
There was a problem hiding this comment.
Separate agent sessions from human approval credentials
When an agent uses the supported session-cookie path, it has the same dashboard credential accepted by app/api/runs/[id]/approvals/[stepIndex]/route.ts, whose POST handler approves and re-enqueues a run using only auth(). The agent can therefore bypass the 403 agent endpoint by posting directly to that existing route, so omitting approval tools does not enforce the human-only gate. Require an agent-specific credential for this surface, or require separate human proof when resolving approvals.
AGENTS.md reference: AGENTS.md:L24-L26
Useful? React with 👍 / 👎.
| latestVersion: latest | ||
| ? { version: latest.version, note: latest.note, steps: latest.steps } | ||
| : null, |
There was a problem hiding this comment.
Redact sensitive values from workflow previews
For workflows containing a fill step with sensitive: true, returning latest.steps verbatim exposes the stored password, OTP, card value, or other private input to the HTTP agent and causes the MCP bridge to serialize it into model context. A workflow preview should redact these values or expose only a placeholder under an explicitly sensitive-read permission rather than treating the full plan as safe metadata.
AGENTS.md reference: AGENTS.md:L123-L124
Useful? React with 👍 / 👎.
| "build": "tsup src/index.ts --format esm --target node20 --clean", | ||
| "start": "node dist/index.js", |
There was a problem hiding this comment.
Bundle the core catalog into the MCP build
After pnpm --filter @ghost/mcp build, this tsup invocation leaves a bare import of @ghost/core/agent in dist/index.js, while that export resolves to packages/core/src/agent/tools.ts. Running the declared node dist/index.js start command on Node 20 therefore exits with ERR_UNKNOWN_FILE_EXTENSION before MCP initializes; bundle @ghost/core or emit consumable JavaScript for the core package.
AGENTS.md reference: AGENTS.md:L47-L51
Useful? React with 👍 / 👎.
| const run = await prisma.run.create({ | ||
| data: { | ||
| orgId: principal.orgId, | ||
| workflowVersionId: version.id, | ||
| triggeredById: principal.userId, |
There was a problem hiding this comment.
Preserve the agent trigger identity in run audits
Every bearer-key start stores only the configured human userId, discarding principal.via; the worker later writes run.started and all step audit events using that same user as the actor. Consequently the audit trail cannot distinguish a dashboard action from an AI-agent/API-key action, so all agent-started runs are systematically misattributed. Persist the trigger channel or credential identity and include it in the audit metadata.
AGENTS.md reference: AGENTS.md:L24-L26
Useful? React with 👍 / 👎.
| }, | ||
| }); | ||
|
|
||
| await enqueueRunWorkflow({ runId: run.id, orgId: principal.orgId }); |
There was a problem hiding this comment.
Clean up runs when queue submission fails
In the agent start path, the Run is committed before BullMQ confirms the enqueue. If Redis is unavailable, invokeAgentTool returns 500 but leaves a permanently QUEUED run; if the enqueue succeeds remotely but its acknowledgment is lost, an agent retry can create and enqueue a second execution. Use an outbox/idempotency key or mark/delete the run when queue submission fails so callers can retry safely.
Useful? React with 👍 / 👎.
| let msg: JsonRpcRequest; | ||
| try { | ||
| msg = JSON.parse(trimmed) as JsonRpcRequest; | ||
| } catch { | ||
| process.stdout.write(fail(null, -32700, "parse error") + "\n"); | ||
| continue; | ||
| } | ||
| const out = await handle(client, msg); |
There was a problem hiding this comment.
Reject non-object JSON-RPC messages without exiting
A syntactically valid JSON line such as null passes this parse and is sent to handle, which immediately destructures it and throws; the top-level catch then terminates the MCP process. A malformed client request should receive JSON-RPC -32600 while the server remains available, so validate that the parsed value is a non-array object before dispatch.
Useful? React with 👍 / 👎.
| "ghost-mcp": "./dist/index.js" | ||
| }, | ||
| "scripts": { | ||
| "dev": "tsx src/index.ts", |
There was a problem hiding this comment.
Exclude optional MCP from the default dev graph
Adding this dev task makes the repository-level turbo run dev launch @ghost/mcp alongside web and worker. With the agent plugin's optional key unset—as in the normal .env.example setup—the MCP process immediately exits with GHOST_AGENT_API_KEY is required, and Turbo terminates the web and worker tasks, so the documented pnpm dev command no longer starts Ghost Cloud unless an optional integration is configured. Launch MCP only through an explicit filtered command or keep it out of the default dev graph.
Useful? React with 👍 / 👎.
| 4. Add to `cloud/.env`: | ||
|
|
||
| ```bash | ||
| GHOST_AGENT_API_KEY="$(openssl rand -hex 32)" |
There was a problem hiding this comment.
Generate the API key before writing the dotenv file
When this line is copied into cloud/.env as instructed, Next's dotenv loader does not execute the command substitution; it sets the API key to the literal, publicly known string $(openssl rand -hex 32). This either leaves a reachable dev server protected by a predictable credential or causes a mismatch when the MCP client uses the actual generated output. Instruct users to run openssl rand -hex 32 separately and paste its output into the dotenv assignment.
Useful? React with 👍 / 👎.
| status: s.status, | ||
| verification: s.verification, | ||
| error: s.error, | ||
| screenshotUrl: s.screenshotKey ? `/api/artifacts/${s.screenshotKey}` : null, |
There was a problem hiding this comment.
Authorize bearer clients for returned screenshot URLs
For the documented MCP/API-key path, get_run returns this artifact URL but app/api/artifacts/[...key]/route.ts authenticates exclusively with auth() and rejects the same bearer key with 401. The agent therefore receives an unusable screenshot reference for every captured step. Resolve the agent principal while preserving the existing organization ownership check, or return a scoped presigned URL suitable for the bearer client.
AGENTS.md reference: AGENTS.md:L124-L124
Useful? React with 👍 / 👎.
| Claude, Cursor, Codex, ChatGPT, and similar tools may **list workflows, preview | ||
| plans, start runs, and observe status**. They **must not approve** sensitive | ||
| steps — that stays human-only in the Ghost UI. |
There was a problem hiding this comment.
Remove the unsupported ChatGPT availability claim
The shipped surface described below consists of local stdio MCP plus the early bearer HTTP API, while this same document explicitly lists shipping ChatGPT remote MCP/OAuth as a non-goal. Naming ChatGPT among clients that can currently list and start runs therefore promises an integration path the repository does not provide. Qualify ChatGPT as future work until a verified remote transport and authentication flow exist.
AGENTS.md reference: AGENTS.md:L96-L97
Useful? React with 👍 / 👎.
Strategy (locked)
Ghost is not another AI agent. It is the governed execution / trust runtime agents plug into:
Claude/Cursor/Codex may list, preview, and start runs. Humans approve sensitive steps in the Ghost UI. Working name may change later; strategy does not wait on naming.
What shipped
docs/product-direction.md, business-model, audiences, automation-strategy,AGENTS.md,cloud/docs/AGENT_PLUGIN.md, site hero copy.@ghost/core/agent— allow-listed tools + explicit forbid list (approve_run, etc.)./api/agent/*— catalog, invoke, workflows/runs/approvals mirrors. Auth: session or bearerGHOST_AGENT_API_KEY(env-bound org/user for local MCP).cloud/apps/mcp— stdio JSON-RPC MCP bridge → HTTP invoke. No approve tools.POST /api/agent/approvalsand forbidden invoke names → 403.Setup
See
cloud/docs/AGENT_PLUGIN.mdandcloud/.env.example(GHOST_AGENT_API_KEY,GHOST_AGENT_ORG_ID,GHOST_AGENT_USER_ID).Validation
pnpm typecheck✅pnpm test✅ (core + mcp + worker; Postgres worker E2E included)pnpm build✅Follow-ups (not in this PR)
Trust
Agents cannot approve. Sensitive steps still halt via
classifyStep→ Ghost UI.