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
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ agentmemory works with any agent that supports hooks, MCP, or REST API. All agen
<sub>MCP server</sub>
</td>
</tr>
<tr>
<td align="center" width="12.5%">
<a href="https://www.warp.dev"><img src="https://github.com/warpdotdev.png?size=120" alt="Warp" width="48" height="48" /></a><br/>
<strong>Warp</strong><br/>
<sub>connect + MCP + skills</sub>
</td>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</tr>
</table>

<p align="center">
Expand Down Expand Up @@ -558,7 +565,12 @@ The agentmemory entry is the **same MCP server block** across every host that us
| **Qwen Code** | `~/.qwen/settings.json` | `agentmemory connect qwen` writes the standard `mcpServers` block. Hook payload is field-compatible with Claude Code, so the existing 12-hook scripts work without modification — wire them via the `hooks` section in the same `settings.json`. |
| **Antigravity** (replaces Gemini CLI) | `mcp_config.json` (in Antigravity's User dir) | `agentmemory connect antigravity` writes the standard `mcpServers` block. macOS: `~/Library/Application Support/Antigravity/User/`. Linux: `~/.config/Antigravity/User/`. Use after the 2026-06-18 Gemini CLI sunset. |
| **Kiro** | `~/.kiro/settings/mcp.json` | `agentmemory connect kiro` writes the user-level config. Workspace overrides go in `.kiro/settings/mcp.json` next to your code. |
| **Goose** | Goose MCP settings UI | Same `mcpServers` block. |
| **Warp** | `~/.warp/.mcp.json` | `agentmemory connect warp` writes the standard `mcpServers` block. Warp also auto-discovers skills from `.claude/skills/` — once the Claude Code plugin is installed the 8 agentmemory skills (`remember`, `recall`, `recap`, `handoff`, `forget`, `commit-context`, `commit-history`, `session-history`) appear natively in Warp's slash-command palette. |
| **Cline (CLI)** | `~/.cline/mcp.json` | `agentmemory connect cline` writes the standard `mcpServers` block. VS Code extension users: paste the same block via Cline Settings → MCP Servers → Edit JSON. |
| **Continue.dev** | `~/.continue/config.yaml` (preferred) or `config.json` (legacy) | `agentmemory connect continue` creates `config.yaml` from scratch when neither exists, or modifies existing `config.json`. **If you already have `config.yaml`** the adapter prints the exact block to paste under `mcpServers:` — it won't silently rewrite your yaml because preserving comments and anchors safely needs a YAML parser the package doesn't ship. Continue uses array form (not object) for `mcpServers`. |
| **Zed** | `~/.config/zed/settings.json` | `agentmemory connect zed` writes under `context_servers` (Zed's key, NOT `mcpServers`). Remote MCP servers can be wired via `{"url": "..."}` instead. |
| **Droid (Factory.ai)** | `~/.factory/mcp.json` | `agentmemory connect droid` writes the standard `mcpServers` block. Project-scoped overrides go in `<repo>/.factory/mcp.json`. The `/mcp` slash command inside droid lists configured servers. |
| **Goose** | Goose MCP settings UI | Same `mcpServers` block — use `goose configure` → Add Extension → MCP. Direct YAML edit at `~/.config/goose/config.yaml` is supported but the schema uses `extensions:` + `cmd` (not `mcpServers:` + `command`). |
| **Aider** | n/a | Talk to the REST API directly: `curl -X POST http://localhost:3111/agentmemory/smart-search -d '{"query": "auth"}'`. |
| **Any agent (32+)** | n/a | `npx skillkit install agentmemory` auto-detects the host and merges. |

Expand Down
19 changes: 19 additions & 0 deletions src/cli/connect/cline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { homedir } from "node:os";
import { join } from "node:path";
import { createJsonMcpAdapter } from "./json-mcp-adapter.js";

// Cline CLI stores MCP server config at ~/.cline/mcp.json with the
// canonical `mcpServers` wrapper — same schema as Claude Code with
// optional `disabled` and `autoApprove` fields per entry.
// VS Code extension users manage MCP through Cline's Settings UI;
// this adapter targets the standalone CLI surface.
// Source: github.com/cline/cline/blob/main/docs/mcp/mcp-overview.mdx
export const adapter = createJsonMcpAdapter({
name: "cline",
displayName: "Cline",
detectDir: join(homedir(), ".cline"),
configPath: join(homedir(), ".cline", "mcp.json"),
docs: "https://github.com/rohitg00/agentmemory#other-agents",
protocolNote:
"→ Using MCP via ~/.cline/mcp.json (CLI). VS Code users: add the same block via Cline Settings → MCP Servers → Edit JSON.",
});
163 changes: 163 additions & 0 deletions src/cli/connect/continue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { dirname, join } from "node:path";
import * as p from "@clack/prompts";
import type { ConnectAdapter, ConnectOptions, ConnectResult } from "./types.js";
import {
AGENTMEMORY_MCP_BLOCK,
backupFile,
logAlreadyWired,
logBackup,
logInstalled,
readJsonSafe,
writeJsonAtomic,
} from "./util.js";

// Continue.dev v1+ prefers ~/.continue/config.yaml; config.json is
// deprecated and ignored when yaml is present. Three branches:
// - config.yaml exists → emit stub with manual edit instructions
// (no YAML dep in tree; preserving comments/anchors safely needs it)
// - config.json exists → modify it (legacy path still loaded when no yaml)
// - neither → create config.yaml from scratch (no merge risk)
// Source: docs.continue.dev/reference/yaml-migration
const CONTINUE_DIR = join(homedir(), ".continue");
const YAML_PATH = join(CONTINUE_DIR, "config.yaml");
const JSON_PATH = join(CONTINUE_DIR, "config.json");

type ContinueEntry = {
name: string;
command: string;
args: string[];
env?: Record<string, string>;
};

type ContinueJsonConfig = {
mcpServers?: ContinueEntry[];
[key: string]: unknown;
};

function buildEntry(): ContinueEntry {
return {
name: "agentmemory",
command: AGENTMEMORY_MCP_BLOCK.command,
args: [...AGENTMEMORY_MCP_BLOCK.args],
env: { ...AGENTMEMORY_MCP_BLOCK.env },
};
}

function entryIsAgentmemory(entry: ContinueEntry | undefined): boolean {
if (!entry) return false;
return entry.name === "agentmemory" && entry.args.includes("@agentmemory/mcp");
}

// Minimal YAML emitter for the agentmemory entry. Quotes string values
// that contain ${ ... } expansion to keep parsers happy. Only used when
// creating a fresh config.yaml — never when modifying an existing one.
function renderFreshYaml(): string {
const e = buildEntry();
const envLines = Object.entries(e.env ?? {})
.map(([k, v]) => ` ${k}: "${v}"`)
.join("\n");
return [
"mcpServers:",
` - name: ${e.name}`,
` command: ${e.command}`,
" args:",
...e.args.map((a) => ` - "${a}"`),
" env:",
envLines,
"",
].join("\n");
}

export const adapter: ConnectAdapter = {
name: "continue",
displayName: "Continue",
docs: "https://github.com/rohitg00/agentmemory#other-agents",
protocolNote:
"→ Using MCP via ~/.continue/config.yaml (preferred) or config.json (legacy, only when no yaml).",

detect(): boolean {
return existsSync(CONTINUE_DIR);
},

async install(opts: ConnectOptions): Promise<ConnectResult> {
const yamlExists = existsSync(YAML_PATH);
const jsonExists = existsSync(JSON_PATH);

// Branch 1: yaml present — refuse to silently mutate user's yaml
// config (preserving comments/anchors needs a proper parser).
if (yamlExists) {
const indented = renderFreshYaml()
.split("\n")
.map((l) => (l ? ` ${l}` : l))
.join("\n");
const manual = `\nMerge this block into ~/.continue/config.yaml (the snippet already includes the top-level mcpServers key — if your config already has a mcpServers list, append the agentmemory entry to it instead of duplicating the key):\n\n${indented}`;
p.log.info(
`Continue: ${YAML_PATH} already exists. Manual edit needed.${manual}`,
);
return { kind: "stub", reason: "config.yaml-needs-manual-edit" };
}

// Branch 2: legacy json present — modify in place.
if (jsonExists) {
const existing = readJsonSafe<ContinueJsonConfig>(JSON_PATH);
const next: ContinueJsonConfig = existing ? { ...existing } : {};
const servers = Array.isArray(next.mcpServers)
? [...next.mcpServers]
: [];

const idx = servers.findIndex((s) => s?.name === "agentmemory");
const alreadyHas = idx >= 0 && entryIsAgentmemory(servers[idx]);
if (alreadyHas && !opts.force) {
logAlreadyWired("Continue", JSON_PATH);
return { kind: "already-wired", mutatedPath: JSON_PATH };
}

if (opts.dryRun) {
p.log.info(
`[dry-run] Would ${alreadyHas ? "overwrite" : "add"} mcpServers[agentmemory] in ${JSON_PATH}`,
);
return { kind: "installed", mutatedPath: JSON_PATH };
}

const backupPath = backupFile(JSON_PATH, "continue");
logBackup(backupPath);

const entry = buildEntry();
if (idx >= 0) servers[idx] = entry;
else servers.push(entry);
next.mcpServers = servers;
writeJsonAtomic(JSON_PATH, next);

const verify = readJsonSafe<ContinueJsonConfig>(JSON_PATH);
const verifyEntry = verify?.mcpServers?.find(
(s) => s?.name === "agentmemory",
);
if (!entryIsAgentmemory(verifyEntry)) {
p.log.error(
`Verification failed: ${JSON_PATH} did not contain mcpServers[agentmemory] after write.`,
);
return { kind: "skipped", reason: "verification-failed" };
}

logInstalled("Continue (legacy config.json)", JSON_PATH);
return {
kind: "installed",
mutatedPath: JSON_PATH,
backupPath,
};
}

// Branch 3: neither exists — create config.yaml from scratch (modern path).
if (opts.dryRun) {
p.log.info(`[dry-run] Would create ${YAML_PATH} with agentmemory entry`);
return { kind: "installed", mutatedPath: YAML_PATH };
}

mkdirSync(dirname(YAML_PATH), { recursive: true });
writeFileSync(YAML_PATH, renderFreshYaml(), "utf-8");
logInstalled("Continue", YAML_PATH);
return { kind: "installed", mutatedPath: YAML_PATH };
},
};
21 changes: 21 additions & 0 deletions src/cli/connect/droid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { homedir } from "node:os";
import { join } from "node:path";
import { createJsonMcpAdapter } from "./json-mcp-adapter.js";

// Factory.ai's Droid CLI stores MCP server config at ~/.factory/mcp.json
// with the canonical `mcpServers` wrapper. Project-scoped overrides live
// at <repo>/.factory/mcp.json. Each entry adds an optional `type` field
// ("stdio" | "http") and `disabled` boolean — agentmemory's stdio block
// works against the same shape without needing the explicit type tag.
// Source: docs.factory.ai/cli/configuration/mcp
export const adapter = createJsonMcpAdapter({
name: "droid",
displayName: "Droid (Factory.ai)",
detectDir: join(homedir(), ".factory"),
configPath: join(homedir(), ".factory", "mcp.json"),
docs: "https://github.com/rohitg00/agentmemory#other-agents",
protocolNote:
"→ Using MCP via ~/.factory/mcp.json. The `/mcp` slash command inside droid lists configured servers.",
// Droid requires `type` per the documented schema. stdio for npx-spawned shim.
extraEntryFields: { type: "stdio" },
});
10 changes: 10 additions & 0 deletions src/cli/connect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ import * as p from "@clack/prompts";
import type { ConnectAdapter, ConnectOptions, ConnectResult } from "./types.js";
import { adapter as antigravity } from "./antigravity.js";
import { adapter as claudeCode } from "./claude-code.js";
import { adapter as cline } from "./cline.js";
import { adapter as codex } from "./codex.js";
import { adapter as continueDev } from "./continue.js";
import { adapter as cursor } from "./cursor.js";
import { adapter as droid } from "./droid.js";
import { adapter as geminiCli } from "./gemini-cli.js";
import { adapter as hermes } from "./hermes.js";
import { adapter as kiro } from "./kiro.js";
import { adapter as openclaw } from "./openclaw.js";
import { adapter as openhuman } from "./openhuman.js";
import { adapter as pi } from "./pi.js";
import { adapter as qwen } from "./qwen.js";
import { adapter as warp } from "./warp.js";
import { adapter as zed } from "./zed.js";

export const ADAPTERS: readonly ConnectAdapter[] = [
claudeCode,
Expand All @@ -21,6 +26,11 @@ export const ADAPTERS: readonly ConnectAdapter[] = [
qwen,
antigravity,
kiro,
warp,
cline,
continueDev,
zed,
droid,
openclaw,
hermes,
pi,
Expand Down
30 changes: 20 additions & 10 deletions src/cli/connect/json-mcp-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ export type JsonMcpAdapterConfig = {
configPath: string;
docs?: string;
protocolNote?: string;
// Wrapper key under which servers live. Default "mcpServers".
// Zed uses "context_servers"; otherwise same shape.
wrapperKey?: string;
// Extra fields merged into the agentmemory entry. Droid requires
// type: "stdio"; other hosts ignore unknown fields.
extraEntryFields?: Record<string, unknown>;
};

type McpEntry = typeof AGENTMEMORY_MCP_BLOCK;
type McpConfig = {
mcpServers?: Record<string, McpEntry>;
[key: string]: unknown;
};
type McpConfig = Record<string, unknown>;

function entryMatches(entry: unknown): boolean {
if (!entry || typeof entry !== "object") return false;
Expand All @@ -38,6 +41,7 @@ function entryMatches(entry: unknown): boolean {
export function createJsonMcpAdapter(
config: JsonMcpAdapterConfig,
): ConnectAdapter {
const wrapperKey = config.wrapperKey ?? "mcpServers";
return {
name: config.name,
displayName: config.displayName,
Expand All @@ -54,7 +58,7 @@ export function createJsonMcpAdapter(
const existing = readJsonSafe<McpConfig>(config.configPath);
const next: McpConfig = existing ? { ...existing } : {};
const servers: Record<string, McpEntry> = {
...((next.mcpServers as Record<string, McpEntry>) ?? {}),
...((next[wrapperKey] as Record<string, McpEntry>) ?? {}),
};

const alreadyHas = entryMatches(servers["agentmemory"]);
Expand All @@ -65,7 +69,7 @@ export function createJsonMcpAdapter(

if (opts.dryRun) {
p.log.info(
`[dry-run] Would ${alreadyHas ? "overwrite" : "add"} mcpServers.agentmemory in ${config.configPath}`,
`[dry-run] Would ${alreadyHas ? "overwrite" : "add"} ${wrapperKey}.agentmemory in ${config.configPath}`,
);
return { kind: "installed", mutatedPath: config.configPath };
}
Expand All @@ -78,14 +82,20 @@ export function createJsonMcpAdapter(
mkdirSync(dirname(config.configPath), { recursive: true });
}

servers["agentmemory"] = AGENTMEMORY_MCP_BLOCK;
next.mcpServers = servers;
servers["agentmemory"] = {
...AGENTMEMORY_MCP_BLOCK,
...(config.extraEntryFields ?? {}),
};
next[wrapperKey] = servers;
writeJsonAtomic(config.configPath, next);

const verify = readJsonSafe<McpConfig>(config.configPath);
if (!entryMatches(verify?.mcpServers?.["agentmemory"])) {
const verifyServers = verify?.[wrapperKey] as
| Record<string, McpEntry>
| undefined;
if (!entryMatches(verifyServers?.["agentmemory"])) {
p.log.error(
`Verification failed: ${config.configPath} did not contain mcpServers.agentmemory after write.`,
`Verification failed: ${config.configPath} did not contain ${wrapperKey}.agentmemory after write.`,
);
return { kind: "skipped", reason: "verification-failed" };
}
Expand Down
19 changes: 19 additions & 0 deletions src/cli/connect/warp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { homedir } from "node:os";
import { join } from "node:path";
import { createJsonMcpAdapter } from "./json-mcp-adapter.js";

// Warp stores MCP server config at ~/.warp/.mcp.json with the
// canonical `mcpServers` wrapper — identical schema to Claude Code.
// Warp also auto-discovers skills from .claude/skills/ so the
// agentmemory plugin's 8 skills are surfaced natively once the
// Claude Code plugin is installed.
// Source: docs.warp.dev/agent-platform/capabilities/mcp/
export const adapter = createJsonMcpAdapter({
name: "warp",
displayName: "Warp",
detectDir: join(homedir(), ".warp"),
configPath: join(homedir(), ".warp", ".mcp.json"),
docs: "https://github.com/rohitg00/agentmemory#other-agents",
protocolNote:
"→ Using MCP via ~/.warp/.mcp.json. Skills auto-discover from .claude/skills/ if the Claude Code plugin is also installed.",
});
22 changes: 22 additions & 0 deletions src/cli/connect/zed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { homedir } from "node:os";
import { join } from "node:path";
import { createJsonMcpAdapter } from "./json-mcp-adapter.js";

// Zed stores its settings (including MCP servers) under "context_servers"
// in settings.json — NOT "mcpServers". User config lives at
// ~/.config/zed/settings.json on all platforms (Zed uses the XDG path
// even on macOS; ~/Library/Application Support/Zed/ holds runtime data
// like the database + cached language servers, not the config).
// Source: zed.dev/docs/ai/mcp + zed.dev/docs/configuring-zed
const zedConfigDir = join(homedir(), ".config", "zed");

export const adapter = createJsonMcpAdapter({
name: "zed",
displayName: "Zed",
detectDir: zedConfigDir,
configPath: join(zedConfigDir, "settings.json"),
wrapperKey: "context_servers",
docs: "https://github.com/rohitg00/agentmemory#other-agents",
protocolNote:
"→ Using MCP via ~/.config/zed/settings.json (key: context_servers).",
});
Loading