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
5 changes: 5 additions & 0 deletions .changeset/host-tool-inventory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"grok-bot-cli": minor
---

Expose cross-host MCP messaging tools: hide Codex tools in Codex and Grok tools in truthfully identified Grok Bot clients, while retaining shared bridge controls.
30 changes: 15 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@
"LICENSE"
],
"devDependencies": {
"@agent-bundle/runtime": "https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@19ab901221cb80ad83b917c7cdbf5eea4e3f3901",
"@agent-bundle/runtime": "https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@57a9196caf2590ece001799eaeed92613352bc21",
"@changesets/cli": "3.0.3",
"@rstest/core": "0.11.12",
"@types/node": "^24.0.0",
"@types/react": "^19.2.18",
"agent-bundle": "https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@19ab901221cb80ad83b917c7cdbf5eea4e3f3901",
"agent-bundle": "https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@57a9196caf2590ece001799eaeed92613352bc21",
"react": "19.3.0",
"react-dom": "19.3.0",
"typescript": "7.0.2",
Expand Down
1 change: 1 addition & 0 deletions src/mcp/grok-bot/tools/codex_send.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const inputSchema = sendSchema.extend({
const resultSchema = z.union([plainResultSchema, relayResultSchema]);
export default defineTool(
{
excludeClients: ['codex'],
description:
'Send to Codex. With replyToGrok or bindingId, managed delivery returns the terminal answer to Grok automatically. Otherwise optional wait observes completion and explicit steer requires expectedTurnId. Acceptance is not completion.',
title: 'Codex send',
Expand Down
1 change: 1 addition & 0 deletions src/mcp/grok-bot/tools/codex_threads.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defineTool } from 'agent-bundle/routes';
import { threadsSchema as inputSchema, resultSchema, threadsOperation, resultText } from '../../../core/codex/routes.js';
export { inputSchema };
export default defineTool({
excludeClients: ['codex'],
description: 'Discover a bounded page of Codex daemon threads.', title: 'Codex threads', annotations: { readOnlyHint: true },
render: { maxElapsedMs: 660000 },
inputSchema, resultSchema,
Expand Down
1 change: 1 addition & 0 deletions src/mcp/grok-bot/tools/codex_wait.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defineTool } from 'agent-bundle/routes';
import { waitSchema as inputSchema, resultSchema, observeOperation, resultText } from '../../../core/codex/routes.js';
export { inputSchema };
export default defineTool({
excludeClients: ['codex'],
description: 'Explicit diagnostic observation of one Codex turn; returns execution and final reply without interrupting it.', title: 'Codex wait', annotations: { readOnlyHint: true },
render: { maxElapsedMs: 660000 },
inputSchema, resultSchema,
Expand Down
1 change: 1 addition & 0 deletions src/mcp/grok-bot/tools/codex_watch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defineTool } from 'agent-bundle/routes';
import { watchSchema as inputSchema, resultSchema, observeOperation, resultText } from '../../../core/codex/routes.js';
export { inputSchema };
export default defineTool({
excludeClients: ['codex'],
description: 'Watch bounded Codex thread events for diagnostics without answering approvals.', title: 'Codex watch', annotations: { readOnlyHint: true },
render: { maxElapsedMs: 660000 },
inputSchema, resultSchema,
Expand Down
1 change: 1 addition & 0 deletions src/mcp/grok-bot/tools/gbot_codex_respond.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
export { inputSchema };
export default defineTool(
{
excludeClients: ['codex'],
description:
'Explicit operator response to a current scoped Codex interaction. Supports only one-time accept/decline/cancel or exact question-ID answers. Never auto-approve.',
title: 'gbot_codex_respond',
Expand Down
1 change: 1 addition & 0 deletions src/mcp/grok-bot/tools/gbot_grok_approvals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defineTool } from 'agent-bundle/routes';
import { listSchema as inputSchema, resultSchema, listOperation } from '../../../core/grok-approval-routes.js';
export { inputSchema };
export default defineTool({
excludeClients: ['grok bot', 'grokbot', 'grok-bot'],
title: 'Pending Grok approvals', description: 'List pending auto-review and local-tool approval cards in the latest 200 entries for a Grok bot. Older or unsupported requests require the owning Grok UI.',
annotations: { readOnlyHint: true }, inputSchema, resultSchema,
inputJsonSchema: { type: 'object', properties: { target: { type: 'string' } }, required: ['target'], additionalProperties: false },
Expand Down
1 change: 1 addition & 0 deletions src/mcp/grok-bot/tools/gbot_grok_respond.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defineTool } from 'agent-bundle/routes';
import { respondSchema as inputSchema, resultSchema, respondOperation } from '../../../core/grok-approval-routes.js';
export { inputSchema };
export default defineTool({
excludeClients: ['grok bot', 'grokbot', 'grok-bot'],
title: 'Respond to Grok approval', description: 'Only after an explicit user decision: accept one current Grok approval once or decline it. Exact target, entryId and approval requestId required. Never auto-approve or grant persistent permissions. Success acknowledges response delivery, not execution.',
annotations: { readOnlyHint: false }, inputSchema, resultSchema,
inputJsonSchema: {
Expand Down
1 change: 1 addition & 0 deletions src/mcp/grok-bot/tools/gbot_send.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
export { inputSchema };
export default defineTool(
{
excludeClients: ['grok bot', 'grokbot', 'grok-bot'],
description:
'Send to Grok Bot. Native Codex calls automatically receive replies in their originating thread; send once and continue work. Without a native source, supply codexThreadId or use manual gbot_thread reading.',
title: 'Send a message to Grok Bot',
Expand Down
1 change: 1 addition & 0 deletions src/mcp/grok-bot/tools/gbot_thread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const inputSchema = z.object({

export default defineTool(
{
excludeClients: ['grok bot', 'grokbot', 'grok-bot'],
annotations: { readOnlyHint: true },
description:
'Read a bounded Grok Bot thread tail. Returns a small receipt by default; pass the last cursor as after for an exclusive client-side delta, or full:true to include bounded entry text.',
Expand Down
8 changes: 8 additions & 0 deletions src/skills/talk-to-grok-bot/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,11 @@ Managed Codex return routes reject `expectedTurnId` and legacy `replyTo`/`envelo
options before submission; use plain `codex_send` for a caller-selected turn guard.
An explicit Grok target supplied with `bindingId` must resolve to the binding's
recipient. A mismatch fails instead of selecting one destination silently.

## Host tool inventory

Codex MCP clients receive Grok messaging and approval tools; truthfully identified
Grok Bot clients receive Codex messaging and approval tools. Bridge start/status/stop
Comment on lines +115 to +116

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove unavailable Codex-tool instructions from the skill

When this bundled skill is loaded by a Codex client, the new inventory correctly omits codex_send, codex_threads, codex_wait, and codex_watch, but the same skill still advertises Codex-thread access through those tools in its frontmatter (line 3) and says the generated Codex plugin exposes them (lines 75–84). This contradictory guidance can make Codex attempt tools that the server now rejects; update the earlier host-agnostic instructions to describe which hosts actually receive the Codex tool set.

Useful? React with 👍 / 👎.

remain shared. Cursor and unknown clients retain both sets. Filtering uses negotiated
client-name prefixes and does not provide authorization. A Grok runtime identifying
itself as Cursor needs its native MCP identity corrected before this filter applies.
1 change: 0 additions & 1 deletion test/desktop-shim.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -947,7 +947,6 @@ test("bridge flushes EOF-tail data before the WS Close", {
`HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${accept}\r\n\r\n`,
"latin1",
));
socket.write(wsServerFrame(0x1, Buffer.from('{"jsonrpc":"2.0","id":1,"result":{}}')));
upgraded = true;
return;
}
Expand Down
30 changes: 30 additions & 0 deletions test/host-tool-inventory.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import { resolve } from 'node:path';
import test from 'node:test';

const sdk = createRequire(import.meta.resolve('agent-bundle/api'));
const { Client } = await import(sdk.resolve('@modelcontextprotocol/client'));
const { StdioClientTransport } = await import(sdk.resolve('@modelcontextprotocol/client/stdio'));
const config = JSON.parse(await readFile(new URL('../artifact/.mcp.json', import.meta.url), 'utf8'));
const entry = config.mcpServers['grok-bot'].args[0].replace('${CLAUDE_PLUGIN_ROOT}', resolve('artifact'));
const codex = ['codex_send', 'codex_threads', 'codex_wait', 'codex_watch', 'gbot_codex_respond'];
const grok = ['gbot_send', 'gbot_thread', 'gbot_grok_approvals', 'gbot_grok_respond'];
const shared = ['gbot_bridge_start', 'gbot_bridge_status', 'gbot_bridge_stop'];

test('built MCP artifact exposes the other host tools and shared bridge controls', async () => {
for (const [name, hidden] of [['codex_cli_rs', codex], ['Grok Bot', grok], ['Cursor', []]]) {
const client = new Client({ name, version: '1.0.0' });
try {
await client.connect(new StdioClientTransport({ command: process.execPath, args: [entry], stderr: 'pipe' }));
const listed = (await client.listTools()).tools.map(tool => tool.name).sort();
assert.deepEqual(listed, [...codex, ...grok, ...shared].filter(tool => !hidden.includes(tool)).sort());
for (const tool of hidden) {
await assert.rejects(client.callTool({ name: tool, arguments: {} }), /disabled|not found/i);
}
} finally {
await client.close();
}
}
});
5 changes: 3 additions & 2 deletions test/relay-surfaces.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export async function fixture({ active = false, interaction, onGateway } = {}) {
export async function mcp(
root,
env,
name = "codex-mcp-client",
name = "cursor",
manifestPath = process.env.RELAY_MCP_MANIFEST ?? "mcp.json",
) {
const manifest = JSON.parse(await readFile(join(root, manifestPath), "utf8"));
Expand Down Expand Up @@ -224,6 +224,7 @@ test(
client = await mcp(
resolve(process.env.RELAY_ARTIFACT_ROOT ?? "artifact"),
f.env,
"codex-mcp-client",
);
const names = (await client.rpc("tools/list", {})).result.tools.map(
(t) => t.name,
Expand All @@ -232,9 +233,9 @@ test(
"gbot_bridge_start",
"gbot_bridge_status",
"gbot_bridge_stop",
"gbot_codex_respond",
])
assert.ok(names.includes(name), name);
assert.ok(!names.includes("gbot_codex_respond"));
const manual = await client.call("gbot_send", {
target: "General",
message: "manual",
Expand Down