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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Turn one-off agent work into something you can rerun: a local MCP client for
with Client ID Metadata Documents (SEP-991).

```bash
npx @kodycodes/cli install
npx @kodycodes/cli login
npx @kodycodes/cli search "what can you do"
npx @kodycodes/cli skill install
Expand All @@ -26,6 +27,7 @@ Or run via `npx @kodycodes/cli` without a global install.

| Command | Purpose |
| --- | --- |
| `kody install` | Detect running local MCP clients, write their config, and start host OAuth. |
| `kody login` | Browser OAuth (CIMD + PKCE). Stores access and refresh tokens. |
| `kody logout` | Deletes stored credentials. |
| `kody status` | Shows login state without printing secrets. |
Expand All @@ -34,6 +36,16 @@ Or run via `npx @kodycodes/cli` without a global install.
| `kody execute` | Calls Kody `execute` (`--code`, `--file`, or stdin via `--file -`). |
| `kody skill install` | Copies the getting-started skill into Claude Code / Cursor / Agents. |

`kody install` lists **running local** agents (Cursor, Claude Desktop, VS Code,
Goose, Claude Code, Codex, Windsurf, Zed, and similar) and writes each host's
remote MCP entry for `https://kody.codes/mcp`. Common host formats go through
[`add-mcp`](https://www.npmjs.com/package/add-mcp). It does not list web clients.
For ChatGPT, Claude.ai, and Grok, use [kody.codes/onboarding](https://kody.codes/onboarding).

After install, the CLI prints a prompt you can paste into the configured agent
to continue onboarding. Host OAuth stays in that client — `kody login` is only
for the CLI itself.

`--mcp-url` or `KODY_MCP_URL` overrides the default `https://kody.codes/mcp`.
`--json` prints structured MCP results.

Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@
"access": "public"
},
"dependencies": {
"@inquirer/checkbox": "^5.2.2",
"@modelcontextprotocol/client": "2.0.0",
"@napi-rs/keyring": "^1.3.0"
"@napi-rs/keyring": "^1.3.0",
"add-mcp": "^2.0.0",
"ps-list": "^9.0.0"
},
"devDependencies": {
"@types/node": "^24.5.2",
Expand Down
10 changes: 10 additions & 0 deletions skills/kody/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ Then install this skill into the current host if it is not already present:
npx @kodycodes/cli skill install
```

To add Kody as a remote MCP server in running local agents:

```bash
npx @kodycodes/cli install
```

That command only lists local clients that are currently running. For web-based
clients (ChatGPT, Claude.ai, Grok), point the user at
https://kody.codes/onboarding.

## Login

```bash
Expand Down
73 changes: 73 additions & 0 deletions src/add-mcp-install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
upsertServer,
type AgentType,
type InstallResult,
type McpServerConfig,
} from 'add-mcp'
import type { HostId } from './host-catalog.js'

/**
* Hosts whose on-disk MCP config `add-mcp` already knows how to merge.
* Claude Desktop is intentionally omitted: remote MCP lives in Connectors, not
* `claude_desktop_config.json`.
*/
export const addMcpAgentByHostId = {
antigravity: 'antigravity',
cline: 'cline',
'cline-cli': 'cline-cli',
'claude-code': 'claude-code',
codex: 'codex',
cursor: 'cursor',
'gemini-cli': 'gemini-cli',
goose: 'goose',
'copilot-cli': 'github-copilot-cli',
'grok-build': 'grok-build',
mcporter: 'mcporter',
opencode: 'opencode',
vscode: 'vscode',
windsurf: 'windsurf',
zed: 'zed',
} as const satisfies Partial<Record<HostId, AgentType>>

export type AddMcpHostId = keyof typeof addMcpAgentByHostId

const addMcpProjectHosts = new Set<AddMcpHostId>([
'claude-code',
'codex',
'cursor',
'gemini-cli',
'copilot-cli',
'grok-build',
'mcporter',
'opencode',
'vscode',
'zed',
])

export type UpsertServerFn = (
agentType: AgentType,
serverName: string,
serverConfig: McpServerConfig,
options?: { local?: boolean; cwd?: string },
) => InstallResult

export function isAddMcpHostId(id: HostId): id is AddMcpHostId {
return Object.hasOwn(addMcpAgentByHostId, id)
}

export function addMcpUsesProjectScope(id: AddMcpHostId, project: boolean): boolean {
return project && addMcpProjectHosts.has(id)
}

export function kodyRemoteConfig(mcpUrl: string): McpServerConfig {
return { type: 'http', url: mcpUrl }
}

export function defaultUpsertServer(
agentType: AgentType,
serverName: string,
serverConfig: McpServerConfig,
options?: { local?: boolean; cwd?: string },
): InstallResult {
return upsertServer(agentType, serverName, serverConfig, options)
}
19 changes: 19 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { usage } from './help.js'
import { ensureFreshCredentials, login } from './auth.js'
import { deleteCredentials, loadCredentials } from './store.js'
import { callKodyTool, formatToolResult, listKodyTools } from './mcp.js'
import { runInstall } from './install.js'
import { installSkill } from './skill.js'
import { readPackageVersion } from './package-info.js'
import { redactError } from './redact.js'
Expand All @@ -16,6 +17,7 @@ export type CommandName =
| 'whoami'
| 'search'
| 'execute'
| 'install'
| 'skill'
| 'help'
| 'version'
Expand Down Expand Up @@ -43,6 +45,8 @@ function parseKnown(args: Array<string>) {
'conversation-id': { type: 'string' },
project: { type: 'boolean' },
'no-browser': { type: 'boolean' },
clients: { type: 'string' },
yes: { type: 'boolean', short: 'y' },
},
})
}
Expand All @@ -68,6 +72,7 @@ export function resolveCommand(argv: Array<string>): {
case 'whoami':
case 'search':
case 'execute':
case 'install':
case 'skill':
case 'help':
case 'version':
Expand Down Expand Up @@ -214,6 +219,20 @@ async function dispatch(
write(formatToolResult(result, json))
return result.isError ? 1 : 0
}
case 'install': {
const result = await runInstall(
{
mcpUrl,
clients:
typeof parsed.values.clients === 'string' ? parsed.values.clients : undefined,
yes: parsed.values.yes === true,
project: parsed.values.project === true,
json,
},
{ stdout: write },
)
return result.code
}
case 'skill': {
const action = parsed.positionals[0] ?? 'install'
if (action !== 'install') {
Expand Down
6 changes: 6 additions & 0 deletions src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ export function cliClientMetadataUrl(mcpUrl: string): string {
export function cliRedirectUrl(port: number = oauthCallbackPort): URL {
return new URL(`http://127.0.0.1:${port}/callback`)
}

export const onboardingPath = '/onboarding'

export function onboardingUrl(mcpUrl: string): string {
return new URL(onboardingPath, mcpUrl).href
}
18 changes: 18 additions & 0 deletions src/detect-running-hosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import psList from 'ps-list'
import { hostCatalog, type HostDefinition } from './host-catalog.js'
import type { ProcessInfo } from './process-info.js'

export async function listRunningProcesses(): Promise<Array<ProcessInfo>> {
const processes = await psList()
return processes.map((process) => ({
name: process.name,
cmd: process.cmd,
}))
}

export function detectRunningHosts(
processes: Array<ProcessInfo>,
catalog: ReadonlyArray<HostDefinition> = hostCatalog,
): Array<HostDefinition> {
return catalog.filter((host) => processes.some((process) => host.matches(process)))
}
10 changes: 9 additions & 1 deletion src/help.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { defaultMcpUrl } from './defaults.js'
import { defaultMcpUrl, onboardingUrl } from './defaults.js'
import { hostIds } from './host-catalog.js'
import { readPackageVersion } from './package-info.js'

export const usage = `Kody CLI ${readPackageVersion()}
Expand All @@ -12,8 +13,15 @@ Usage:
kody whoami [--mcp-url <url>] [--json]
kody search [query] [--entity <ref>] [--domain <id>] [--limit <n>] [--json]
kody execute [--code <esm>] [--file <path>] [--params <json>] [--conversation-id <id>] [--json]
kody install [--mcp-url <url>] [--clients <ids>] [--yes] [--project] [--json]
kody skill install [--project]

kody install configures running local MCP clients (Cursor, Claude Desktop,
VS Code, Goose, and others). For web-based clients (ChatGPT, Claude.ai, Grok),
see ${onboardingUrl(defaultMcpUrl)}

--clients Comma-separated ids: ${hostIds.join(', ')}

Environment:
KODY_MCP_URL Override the default MCP URL (${defaultMcpUrl})
`
Loading