diff --git a/README.md b/README.md
index 598ac5b0..f68bceee 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,7 @@
[](#)
[](#)
[](#)
+[](#)
@@ -41,7 +42,7 @@ npx @colbymchenry/codegraph # zero-install, or:
npm i -g @colbymchenry/codegraph
```
-CodeGraph bundles its own runtime — nothing to compile, no native build, works the same everywhere. The interactive installer auto-configures your agent(s) — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent.
+CodeGraph bundles its own runtime — nothing to compile, no native build, works the same everywhere. The interactive installer auto-configures your agent(s) — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Kiro.
### Initialize Projects
@@ -161,7 +162,7 @@ npx @colbymchenry/codegraph
```
The installer will:
-- Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**
+- Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Kiro**
- Prompt to install `codegraph` on your PATH (so agents can launch the MCP server)
- Ask whether configs apply to all your projects or just this one
- Write each chosen agent's MCP server config + an instructions file (e.g. `CLAUDE.md`, `.cursor/rules/codegraph.mdc`, `~/.codex/AGENTS.md`)
@@ -187,7 +188,7 @@ codegraph install --print-config codex # print snippet, no file wr
### 2. Restart Your Agent
-Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent) for the MCP server to load.
+Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent / Kiro) for the MCP server to load.
### 3. Initialize Projects
diff --git a/__tests__/installer-targets.test.ts b/__tests__/installer-targets.test.ts
index 44e90d68..28cf6ac7 100644
--- a/__tests__/installer-targets.test.ts
+++ b/__tests__/installer-targets.test.ts
@@ -363,6 +363,33 @@ describe('Installer targets — partial-state idempotency', () => {
expect(body).toContain('custom:\n keep: true');
});
+ it('kiro: writes to .kiro/settings/mcp.json (nested settings dir, not .kiro root)', () => {
+ const kiro = getTarget('kiro')!;
+ const local = kiro.install('local', { autoAllow: false });
+ expect(local.files[0].path.replace(/\\/g, '/')).toMatch(/\/\.kiro\/settings\/mcp\.json$/);
+ expect(fs.existsSync(path.join(tmpCwd, '.kiro', 'mcp.json'))).toBe(false);
+
+ const global = kiro.install('global', { autoAllow: false });
+ expect(global.files[0].path.replace(/\\/g, '/')).toMatch(/\/\.kiro\/settings\/mcp\.json$/);
+ expect(fs.existsSync(path.join(tmpHome, '.kiro', 'mcp.json'))).toBe(false);
+ });
+
+ it('kiro: install writes mcpServers.codegraph and uninstall strips it cleanly', () => {
+ const kiro = getTarget('kiro')!;
+ kiro.install('local', { autoAllow: false });
+ const file = path.join(tmpCwd, '.kiro', 'settings', 'mcp.json');
+ const after = JSON.parse(fs.readFileSync(file, 'utf-8'));
+ expect(after.mcpServers.codegraph).toEqual({
+ type: 'stdio',
+ command: 'codegraph',
+ args: ['serve', '--mcp'],
+ });
+
+ kiro.uninstall('local');
+ const final = JSON.parse(fs.readFileSync(file, 'utf-8'));
+ expect(final.mcpServers).toBeUndefined();
+ });
+
it('opencode: uninstall removes only mcp.codegraph, preserves comments and siblings', () => {
const opencode = getTarget('opencode')!;
const dir = path.join(tmpHome, '.config', 'opencode');
@@ -616,6 +643,7 @@ describe('Installer targets — registry', () => {
expect(getTarget('codex')?.id).toBe('codex');
expect(getTarget('opencode')?.id).toBe('opencode');
expect(getTarget('hermes')?.id).toBe('hermes');
+ expect(getTarget('kiro')?.id).toBe('kiro');
expect(getTarget('not-a-real-target')).toBeUndefined();
});
diff --git a/src/bin/codegraph.ts b/src/bin/codegraph.ts
index dac8ce1e..b6c9100d 100644
--- a/src/bin/codegraph.ts
+++ b/src/bin/codegraph.ts
@@ -1341,7 +1341,7 @@ program
*/
program
.command('install')
- .description('Install codegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent)')
+ .description('Install codegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Kiro)')
.option('-t, --target ', 'Target agent(s): comma-separated ids, or "auto"|"all"|"none". Default: prompt')
.option('-l, --location ', 'Install location: "global" or "local". Default: prompt')
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=auto, auto-allow on')
diff --git a/src/installer/targets/kiro.ts b/src/installer/targets/kiro.ts
new file mode 100644
index 00000000..102710af
--- /dev/null
+++ b/src/installer/targets/kiro.ts
@@ -0,0 +1,124 @@
+/**
+ * Kiro target (kiro.dev).
+ *
+ * - MCP server entry to `~/.kiro/settings/mcp.json` (global = user
+ * scope) or `./.kiro/settings/mcp.json` (local = workspace scope).
+ * Same `{ mcpServers: { codegraph: {...} } }` shape as Claude /
+ * Cursor. Kiro auto-reconnects on file save, so no restart note is
+ * emitted (unlike Cursor).
+ * Docs: https://kiro.dev/docs/mcp/configuration/
+ *
+ * Kiro reads both files and merges with workspace precedence — same
+ * pattern as Cursor and opencode. Nothing here needs an `--path`
+ * workaround like Cursor's: Kiro launches MCP servers with the
+ * workspace as cwd, so codegraph's normal `process.cwd()` resolution
+ * finds `.codegraph/` correctly.
+ *
+ * No permissions / auto-allow surface — Kiro doesn't expose one the
+ * installer can populate, so `autoAllow` is silently ignored. No
+ * project-local instructions / steering surface is written by this
+ * target; users who want a Kiro steering file can drop one under
+ * `.kiro/steering/` themselves.
+ */
+
+import * as fs from 'fs';
+import * as path from 'path';
+import * as os from 'os';
+import {
+ AgentTarget,
+ DetectionResult,
+ InstallOptions,
+ Location,
+ WriteResult,
+} from './types';
+import {
+ getMcpServerConfig,
+ jsonDeepEqual,
+ readJsonFile,
+ writeJsonFile,
+} from './shared';
+
+function kiroConfigDir(loc: Location): string {
+ return loc === 'global'
+ ? path.join(os.homedir(), '.kiro')
+ : path.join(process.cwd(), '.kiro');
+}
+
+function mcpJsonPath(loc: Location): string {
+ return path.join(kiroConfigDir(loc), 'settings', 'mcp.json');
+}
+
+class KiroTarget implements AgentTarget {
+ readonly id = 'kiro' as const;
+ readonly displayName = 'Kiro';
+ readonly docsUrl = 'https://kiro.dev/docs/mcp/configuration/';
+
+ supportsLocation(_loc: Location): boolean {
+ return true;
+ }
+
+ detect(loc: Location): DetectionResult {
+ const mcpPath = mcpJsonPath(loc);
+ const config = readJsonFile(mcpPath);
+ const alreadyConfigured = !!config.mcpServers?.codegraph;
+ // "Installed" heuristic: presence of a `.kiro` dir at the
+ // location. Documented Kiro install flow doesn't promise a binary
+ // on PATH, but both the user-global config and the workspace
+ // config live under `.kiro/`, so its existence is the strongest
+ // signal we have.
+ const installed = fs.existsSync(kiroConfigDir(loc));
+ return { installed, alreadyConfigured, configPath: mcpPath };
+ }
+
+ install(loc: Location, _opts: InstallOptions): WriteResult {
+ return { files: [writeMcpEntry(loc)] };
+ }
+
+ uninstall(loc: Location): WriteResult {
+ const mcpPath = mcpJsonPath(loc);
+ const config = readJsonFile(mcpPath);
+ if (!config.mcpServers?.codegraph) {
+ return { files: [{ path: mcpPath, action: 'not-found' }] };
+ }
+ delete config.mcpServers.codegraph;
+ if (Object.keys(config.mcpServers).length === 0) {
+ delete config.mcpServers;
+ }
+ writeJsonFile(mcpPath, config);
+ return { files: [{ path: mcpPath, action: 'removed' }] };
+ }
+
+ printConfig(loc: Location): string {
+ const target = mcpJsonPath(loc);
+ const snippet = JSON.stringify(
+ { mcpServers: { codegraph: getMcpServerConfig() } },
+ null,
+ 2,
+ );
+ return `# Add to ${target}\n\n${snippet}\n`;
+ }
+
+ describePaths(loc: Location): string[] {
+ return [mcpJsonPath(loc)];
+ }
+}
+
+function writeMcpEntry(loc: Location): WriteResult['files'][number] {
+ const file = mcpJsonPath(loc);
+ const existing = readJsonFile(file);
+ const before = existing.mcpServers?.codegraph;
+ const after = getMcpServerConfig();
+
+ if (jsonDeepEqual(before, after)) {
+ return { path: file, action: 'unchanged' };
+ }
+ const action: 'created' | 'updated' = before
+ ? 'updated'
+ : (fs.existsSync(file) ? 'updated' : 'created');
+ if (!existing.mcpServers) existing.mcpServers = {};
+ existing.mcpServers.codegraph = after;
+ writeJsonFile(file, existing);
+ return { path: file, action };
+}
+
+export const kiroTarget: AgentTarget = new KiroTarget();
diff --git a/src/installer/targets/registry.ts b/src/installer/targets/registry.ts
index 0091ab64..3a0322d8 100644
--- a/src/installer/targets/registry.ts
+++ b/src/installer/targets/registry.ts
@@ -13,6 +13,7 @@ import { cursorTarget } from './cursor';
import { codexTarget } from './codex';
import { opencodeTarget } from './opencode';
import { hermesTarget } from './hermes';
+import { kiroTarget } from './kiro';
export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([
claudeTarget,
@@ -20,6 +21,7 @@ export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([
codexTarget,
opencodeTarget,
hermesTarget,
+ kiroTarget,
]);
export function getTarget(id: string): AgentTarget | undefined {
diff --git a/src/installer/targets/types.ts b/src/installer/targets/types.ts
index 290f13ce..5343bd95 100644
--- a/src/installer/targets/types.ts
+++ b/src/installer/targets/types.ts
@@ -19,7 +19,7 @@ export type Location = 'global' | 'local';
* lookup. New targets add a value here when they're added to the
* registry. Keep these short and lowercase.
*/
-export type TargetId = 'claude' | 'cursor' | 'codex' | 'opencode' | 'hermes';
+export type TargetId = 'claude' | 'cursor' | 'codex' | 'opencode' | 'hermes' | 'kiro';
/**
* Result of `target.detect(location)`.