mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-04 10:14:47 +08:00
refactor(skill/atomic-chat-tool): ship MCP file in skill folder, revert src edits
The initial /add-atomic-chat-tool merge added src edits directly to main. That conflicts with the utility-skill pattern used elsewhere (e.g. /claw): the skill folder should ship the file and SKILL.md should instruct copy + idempotent edits at install time, not a git merge that carries src diffs. - Move container/agent-runner/src/atomic-chat-mcp-stdio.ts → .claude/skills/add-atomic-chat-tool/atomic-chat-mcp-stdio.ts - Revert the atomic_chat mcpServers entry in agent-runner index.ts - Revert mcp__atomic_chat__* from TOOL_ALLOWLIST in providers/claude.ts - Revert ATOMIC_CHAT_* env forwarding and [ATOMIC] log elevation in src/container-runner.ts - Empty .env.example back out - Rewrite SKILL.md: copy the shipped file, then apply deterministic Edits (index.ts, providers/claude.ts, container-runner.ts, .env.example) with exact before/after snippets the installer agent can match. Main is now back to its pre-PR state for the tool; /add-atomic-chat-tool re-applies everything at install time. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@ Tools exposed:
|
|||||||
|
|
||||||
Model management (download, delete) is done through the **Atomic Chat desktop UI** — the app is a fork of Jan and manages its own model library.
|
Model management (download, delete) is done through the **Atomic Chat desktop UI** — the app is a fork of Jan and manages its own model library.
|
||||||
|
|
||||||
|
The skill ships the MCP server source in this folder and copies it into the agent-runner tree at install time, then wires it up with small edits to `index.ts`, `providers/claude.ts`, and `container-runner.ts`. No branch merge — all edits are additive and idempotent.
|
||||||
|
|
||||||
## Phase 1: Pre-flight
|
## Phase 1: Pre-flight
|
||||||
|
|
||||||
### Check if already applied
|
### Check if already applied
|
||||||
@@ -37,42 +39,128 @@ If the request fails:
|
|||||||
|
|
||||||
## Phase 2: Apply Code Changes
|
## Phase 2: Apply Code Changes
|
||||||
|
|
||||||
### Ensure upstream remote
|
### Copy the MCP server source
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git remote -v
|
cp .claude/skills/add-atomic-chat-tool/atomic-chat-mcp-stdio.ts container/agent-runner/src/atomic-chat-mcp-stdio.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
If `upstream` is missing, add it:
|
### Register the MCP server in the agent-runner
|
||||||
|
|
||||||
|
Edit `container/agent-runner/src/index.ts`. Find the `mcpServers` object that currently looks like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const mcpServers: Record<string, { command: string; args: string[]; env: Record<string, string> }> = {
|
||||||
|
nanoclaw: {
|
||||||
|
command: 'bun',
|
||||||
|
args: ['run', mcpServerPath],
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Add an `atomic_chat` entry alongside `nanoclaw`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const mcpServers: Record<string, { command: string; args: string[]; env: Record<string, string> }> = {
|
||||||
|
nanoclaw: {
|
||||||
|
command: 'bun',
|
||||||
|
args: ['run', mcpServerPath],
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
|
atomic_chat: {
|
||||||
|
command: 'bun',
|
||||||
|
args: ['run', path.join(__dirname, 'atomic-chat-mcp-stdio.ts')],
|
||||||
|
env: {
|
||||||
|
...(process.env.ATOMIC_CHAT_HOST ? { ATOMIC_CHAT_HOST: process.env.ATOMIC_CHAT_HOST } : {}),
|
||||||
|
...(process.env.ATOMIC_CHAT_API_KEY ? { ATOMIC_CHAT_API_KEY: process.env.ATOMIC_CHAT_API_KEY } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add the tool glob to the allowlist
|
||||||
|
|
||||||
|
Edit `container/agent-runner/src/providers/claude.ts`. Find `'mcp__nanoclaw__*',` in the `TOOL_ALLOWLIST` array and add `'mcp__atomic_chat__*',` on the following line:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
'mcp__nanoclaw__*',
|
||||||
|
'mcp__atomic_chat__*',
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Forward host env vars into the container
|
||||||
|
|
||||||
|
Edit `src/container-runner.ts` in `buildContainerArgs`. Find the `TZ` env line:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
args.push('-e', `TZ=${TIMEZONE}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
Add ATOMIC_CHAT forwarding right after it:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
args.push('-e', `TZ=${TIMEZONE}`);
|
||||||
|
|
||||||
|
// Atomic Chat MCP tool: forward host overrides if set (default is host.docker.internal:1337).
|
||||||
|
if (process.env.ATOMIC_CHAT_HOST) {
|
||||||
|
args.push('-e', `ATOMIC_CHAT_HOST=${process.env.ATOMIC_CHAT_HOST}`);
|
||||||
|
}
|
||||||
|
if (process.env.ATOMIC_CHAT_API_KEY) {
|
||||||
|
args.push('-e', `ATOMIC_CHAT_API_KEY=${process.env.ATOMIC_CHAT_API_KEY}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Surface `[ATOMIC]` log lines at info level
|
||||||
|
|
||||||
|
In the same file, find the stderr logger:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
container.stderr?.on('data', (data) => {
|
||||||
|
for (const line of data.toString().trim().split('\n')) {
|
||||||
|
if (line) log.debug(line, { container: agentGroup.folder });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace it with:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
container.stderr?.on('data', (data) => {
|
||||||
|
for (const line of data.toString().trim().split('\n')) {
|
||||||
|
if (!line) continue;
|
||||||
|
if (line.includes('[ATOMIC]')) {
|
||||||
|
log.info(line, { container: agentGroup.folder });
|
||||||
|
} else {
|
||||||
|
log.debug(line, { container: agentGroup.folder });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add env-var stubs to `.env.example`
|
||||||
|
|
||||||
|
Append to `.env.example`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git remote add upstream https://github.com/qwibitai/nanoclaw.git
|
# Atomic Chat MCP tool (.claude/skills/add-atomic-chat-tool)
|
||||||
|
# Override the host where Atomic Chat exposes its OpenAI-compatible API.
|
||||||
|
# Default: http://host.docker.internal:1337 (with fallback to localhost)
|
||||||
|
# ATOMIC_CHAT_HOST=http://host.docker.internal:1337
|
||||||
|
|
||||||
|
# Optional API key. Leave unset for a local Atomic Chat install — it does not require auth.
|
||||||
|
# ATOMIC_CHAT_API_KEY=
|
||||||
```
|
```
|
||||||
|
|
||||||
### Merge the skill branch
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git fetch upstream skill/atomic-chat-tool
|
|
||||||
git merge upstream/skill/atomic-chat-tool
|
|
||||||
```
|
|
||||||
|
|
||||||
This merges in:
|
|
||||||
- `container/agent-runner/src/atomic-chat-mcp-stdio.ts` (Atomic Chat MCP server, run directly via `bun`)
|
|
||||||
- Atomic Chat MCP registration in `container/agent-runner/src/index.ts` (`mcpServers.atomic_chat`)
|
|
||||||
- `mcp__atomic_chat__*` added to `TOOL_ALLOWLIST` in `container/agent-runner/src/providers/claude.ts`
|
|
||||||
- `[ATOMIC]` log surfacing and `ATOMIC_CHAT_HOST` / `ATOMIC_CHAT_API_KEY` forwarding in `src/container-runner.ts`
|
|
||||||
- `ATOMIC_CHAT_HOST` / `ATOMIC_CHAT_API_KEY` stubs in `.env.example`
|
|
||||||
|
|
||||||
If the merge reports conflicts, resolve them by reading the conflicted files and understanding the intent of both sides.
|
|
||||||
|
|
||||||
### Validate code changes
|
### Validate code changes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run build
|
pnpm run build
|
||||||
|
pnpm exec tsc -p container/agent-runner/tsconfig.json --noEmit
|
||||||
./container/build.sh
|
./container/build.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Build must be clean before proceeding.
|
All three must be clean before proceeding.
|
||||||
|
|
||||||
## Phase 3: Configure
|
## Phase 3: Configure
|
||||||
|
|
||||||
@@ -126,9 +214,10 @@ Look for:
|
|||||||
### Agent says "Atomic Chat is not installed" or tries to run a CLI
|
### Agent says "Atomic Chat is not installed" or tries to run a CLI
|
||||||
|
|
||||||
The agent is looking for a CLI that doesn't exist instead of using the MCP tools. This means:
|
The agent is looking for a CLI that doesn't exist instead of using the MCP tools. This means:
|
||||||
1. The MCP server wasn't registered — check `container/agent-runner/src/index.ts` has the `atomic_chat` entry in `mcpServers`
|
1. The MCP server wasn't copied — check `container/agent-runner/src/atomic-chat-mcp-stdio.ts` exists
|
||||||
2. The allowlist wasn't updated — check `container/agent-runner/src/providers/claude.ts` includes `mcp__atomic_chat__*` in `TOOL_ALLOWLIST`
|
2. The MCP server wasn't registered — check `container/agent-runner/src/index.ts` has the `atomic_chat` entry in `mcpServers`
|
||||||
3. The container wasn't rebuilt — run `./container/build.sh`
|
3. The allowlist wasn't updated — check `container/agent-runner/src/providers/claude.ts` includes `mcp__atomic_chat__*` in `TOOL_ALLOWLIST`
|
||||||
|
4. The container wasn't rebuilt — run `./container/build.sh`
|
||||||
|
|
||||||
### "Failed to connect to Atomic Chat"
|
### "Failed to connect to Atomic Chat"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
# Atomic Chat MCP tool (skill/atomic-chat-tool)
|
|
||||||
# Override the host where Atomic Chat exposes its OpenAI-compatible API.
|
|
||||||
# Default: http://host.docker.internal:1337 (with fallback to localhost)
|
|
||||||
# ATOMIC_CHAT_HOST=http://host.docker.internal:1337
|
|
||||||
|
|
||||||
# Optional API key. Leave unset for a local Atomic Chat install — it does not require auth.
|
|
||||||
# ATOMIC_CHAT_API_KEY=
|
|
||||||
|
|||||||
@@ -79,14 +79,6 @@ async function main(): Promise<void> {
|
|||||||
args: ['run', mcpServerPath],
|
args: ['run', mcpServerPath],
|
||||||
env: {},
|
env: {},
|
||||||
},
|
},
|
||||||
atomic_chat: {
|
|
||||||
command: 'bun',
|
|
||||||
args: ['run', path.join(__dirname, 'atomic-chat-mcp-stdio.ts')],
|
|
||||||
env: {
|
|
||||||
...(process.env.ATOMIC_CHAT_HOST ? { ATOMIC_CHAT_HOST: process.env.ATOMIC_CHAT_HOST } : {}),
|
|
||||||
...(process.env.ATOMIC_CHAT_API_KEY ? { ATOMIC_CHAT_API_KEY: process.env.ATOMIC_CHAT_API_KEY } : {}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ const TOOL_ALLOWLIST = [
|
|||||||
'Skill',
|
'Skill',
|
||||||
'NotebookEdit',
|
'NotebookEdit',
|
||||||
'mcp__nanoclaw__*',
|
'mcp__nanoclaw__*',
|
||||||
'mcp__atomic_chat__*',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
interface SDKUserMessage {
|
interface SDKUserMessage {
|
||||||
|
|||||||
+1
-14
@@ -139,12 +139,7 @@ async function spawnContainer(session: Session): Promise<void> {
|
|||||||
// Log stderr
|
// Log stderr
|
||||||
container.stderr?.on('data', (data) => {
|
container.stderr?.on('data', (data) => {
|
||||||
for (const line of data.toString().trim().split('\n')) {
|
for (const line of data.toString().trim().split('\n')) {
|
||||||
if (!line) continue;
|
if (line) log.debug(line, { container: agentGroup.folder });
|
||||||
if (line.includes('[ATOMIC]')) {
|
|
||||||
log.info(line, { container: agentGroup.folder });
|
|
||||||
} else {
|
|
||||||
log.debug(line, { container: agentGroup.folder });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -401,14 +396,6 @@ async function buildContainerArgs(
|
|||||||
// Everything NanoClaw-specific is in container.json (read by runner at startup).
|
// Everything NanoClaw-specific is in container.json (read by runner at startup).
|
||||||
args.push('-e', `TZ=${TIMEZONE}`);
|
args.push('-e', `TZ=${TIMEZONE}`);
|
||||||
|
|
||||||
// Atomic Chat MCP tool: forward host overrides if set (default is host.docker.internal:1337).
|
|
||||||
if (process.env.ATOMIC_CHAT_HOST) {
|
|
||||||
args.push('-e', `ATOMIC_CHAT_HOST=${process.env.ATOMIC_CHAT_HOST}`);
|
|
||||||
}
|
|
||||||
if (process.env.ATOMIC_CHAT_API_KEY) {
|
|
||||||
args.push('-e', `ATOMIC_CHAT_API_KEY=${process.env.ATOMIC_CHAT_API_KEY}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provider-contributed env vars (e.g. XDG_DATA_HOME, OPENCODE_*, NO_PROXY).
|
// Provider-contributed env vars (e.g. XDG_DATA_HOME, OPENCODE_*, NO_PROXY).
|
||||||
if (providerContribution.env) {
|
if (providerContribution.env) {
|
||||||
for (const [key, value] of Object.entries(providerContribution.env)) {
|
for (const [key, value] of Object.entries(providerContribution.env)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user