@kata-sh/cli 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -6
- package/dist/cli.js +20 -0
- package/dist/loader.js +10 -0
- package/dist/resource-loader.js +19 -0
- package/package.json +1 -1
- package/src/resources/AGENTS.md +154 -2
- package/src/resources/extensions/kata/preferences.ts +18 -6
- package/src/resources/extensions/kata/tests/preferences-frontmatter.test.mjs +53 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Kata CLI
|
|
2
2
|
|
|
3
|
-
A terminal coding agent built on [pi](https://github.com/badlogic/pi-mono) (`@mariozechner/pi-coding-agent`). Kata CLI bundles a curated set of extensions for structured planning, browser automation, web search, subagent orchestration, and more.
|
|
3
|
+
A terminal coding agent built on [pi](https://github.com/badlogic/pi-mono) (`@mariozechner/pi-coding-agent`). Kata CLI bundles a curated set of extensions for structured planning, browser automation, web search, subagent orchestration, MCP server integration, and more.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
@@ -28,7 +28,7 @@ Kata CLI is a thin wrapper around pi-coding-agent. It does not fork pi — it co
|
|
|
28
28
|
apps/cli/
|
|
29
29
|
src/
|
|
30
30
|
loader.ts — Entry point: sets KATA_* env vars, imports cli.ts
|
|
31
|
-
cli.ts — Calls
|
|
31
|
+
cli.ts — Calls createAgentSession() + InteractiveMode
|
|
32
32
|
app-paths.ts — ~/.kata-cli/ path constants
|
|
33
33
|
resource-loader.ts — Syncs bundled resources to ~/.kata-cli/agent/
|
|
34
34
|
wizard.ts — First-run setup, env key hydration
|
|
@@ -47,8 +47,12 @@ apps/cli/
|
|
|
47
47
|
|
|
48
48
|
1. `loader.ts` sets `PI_PACKAGE_DIR` to `pkg/` so pi reads Kata's branding config
|
|
49
49
|
2. `loader.ts` sets `KATA_CODING_AGENT_DIR` so pi uses `~/.kata-cli/agent/` instead of `~/.pi/agent/`
|
|
50
|
-
3. `
|
|
51
|
-
4. `
|
|
50
|
+
3. `loader.ts` injects `--mcp-config ~/.kata-cli/agent/mcp.json` into `process.argv` for the MCP adapter
|
|
51
|
+
4. `resource-loader.ts` syncs bundled extensions, agents, skills, and `AGENTS.md` to `~/.kata-cli/agent/` on every launch
|
|
52
|
+
5. `resource-loader.ts` scaffolds a starter `mcp.json` on first launch (never overwrites existing config)
|
|
53
|
+
6. `cli.ts` seeds `npm:pi-mcp-adapter` into settings so pi auto-installs it
|
|
54
|
+
7. `cli.ts` injects the `mcp-config` flag into the extension runtime (required because Kata bypasses pi's `main()` and its two-pass argv parsing)
|
|
55
|
+
8. `cli.ts` calls `createAgentSession()` + `InteractiveMode` — pi handles everything from there
|
|
52
56
|
|
|
53
57
|
## Bundled Extensions
|
|
54
58
|
|
|
@@ -64,6 +68,136 @@ apps/cli/
|
|
|
64
68
|
| `mac-tools/` | macOS-specific utilities |
|
|
65
69
|
| `shared/` | Shared UI components (library, not an entry point) |
|
|
66
70
|
|
|
71
|
+
## MCP Support
|
|
72
|
+
|
|
73
|
+
Kata ships with [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) support via [`pi-mcp-adapter`](https://github.com/nicobailon/pi-mcp-adapter), auto-installed on first launch. One proxy `mcp` tool (~200 tokens in context) gives the agent on-demand access to any MCP server's tools without burning context on individual tool definitions.
|
|
74
|
+
|
|
75
|
+
### How It Works
|
|
76
|
+
|
|
77
|
+
The MCP integration has three parts:
|
|
78
|
+
|
|
79
|
+
1. **Package seeding**: `cli.ts` ensures `npm:pi-mcp-adapter` is in the settings packages list on every startup. Pi's package manager auto-installs it globally if missing.
|
|
80
|
+
2. **Config path injection**: `loader.ts` pushes `--mcp-config` into `process.argv` and `cli.ts` sets the flag on `runtime.flagValues` — both are needed because the adapter reads the config path at two different points in its lifecycle.
|
|
81
|
+
3. **Config scaffolding**: `resource-loader.ts` creates a starter `~/.kata-cli/agent/mcp.json` on first launch. Never overwrites existing config.
|
|
82
|
+
|
|
83
|
+
### Adding MCP Servers
|
|
84
|
+
|
|
85
|
+
Edit `~/.kata-cli/agent/mcp.json`:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"settings": {
|
|
90
|
+
"toolPrefix": "server",
|
|
91
|
+
"idleTimeout": 10
|
|
92
|
+
},
|
|
93
|
+
"mcpServers": {}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Example: Linear (OAuth via mcp-remote)
|
|
98
|
+
|
|
99
|
+
Many hosted MCP servers (Linear, etc.) use OAuth 2.1 authentication. These require [`mcp-remote`](https://github.com/geelen/mcp-remote) as a stdio proxy that handles the browser-based OAuth flow:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"settings": { "toolPrefix": "server", "idleTimeout": 10 },
|
|
104
|
+
"mcpServers": {
|
|
105
|
+
"linear": {
|
|
106
|
+
"command": "npx",
|
|
107
|
+
"args": ["-y", "mcp-remote", "https://mcp.linear.app/mcp"]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
After adding the config and restarting Kata:
|
|
114
|
+
|
|
115
|
+
1. Connect the server (opens browser for OAuth):
|
|
116
|
+
```
|
|
117
|
+
mcp({ connect: "linear" })
|
|
118
|
+
```
|
|
119
|
+
2. Authorize in the browser when prompted by Linear.
|
|
120
|
+
3. Use tools:
|
|
121
|
+
```
|
|
122
|
+
mcp({ server: "linear" }) — list all Linear tools
|
|
123
|
+
mcp({ search: "issues" }) — search for issue-related tools
|
|
124
|
+
mcp({ tool: "linear_list_teams" }) — call a tool
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Tokens are cached in `~/.mcp-auth/` for subsequent sessions. If you hit errors, clear cached auth with `rm -rf ~/.mcp-auth` and reconnect.
|
|
128
|
+
|
|
129
|
+
#### Example: Stdio server with env vars
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"mcpServers": {
|
|
134
|
+
"my-server": {
|
|
135
|
+
"command": "npx",
|
|
136
|
+
"args": ["-y", "some-mcp-server"],
|
|
137
|
+
"env": {
|
|
138
|
+
"API_KEY": "${MY_API_KEY}"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Environment variables support `${VAR}` interpolation from `process.env`.
|
|
146
|
+
|
|
147
|
+
#### Example: HTTP server with bearer token
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"mcpServers": {
|
|
152
|
+
"my-api": {
|
|
153
|
+
"url": "https://api.example.com/mcp",
|
|
154
|
+
"auth": "bearer",
|
|
155
|
+
"bearerTokenEnv": "MY_API_KEY"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### Importing existing configs
|
|
162
|
+
|
|
163
|
+
Pull in your existing Claude Code, Cursor, or VS Code MCP configuration:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"imports": ["claude-code", "cursor"],
|
|
168
|
+
"mcpServers": {}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Supported: `cursor`, `claude-code`, `claude-desktop`, `vscode`, `windsurf`, `codex`.
|
|
173
|
+
|
|
174
|
+
### Server Lifecycle
|
|
175
|
+
|
|
176
|
+
| Mode | Behavior |
|
|
177
|
+
|------|----------|
|
|
178
|
+
| `lazy` (default) | Connect on first tool call. Disconnect after idle timeout. Cached metadata keeps search/list working offline. |
|
|
179
|
+
| `eager` | Connect at startup. No auto-reconnect on drop. |
|
|
180
|
+
| `keep-alive` | Connect at startup. Auto-reconnect via health checks. |
|
|
181
|
+
|
|
182
|
+
### Usage Reference
|
|
183
|
+
|
|
184
|
+
| Command | Description |
|
|
185
|
+
|---------|-------------|
|
|
186
|
+
| `mcp({ })` | Show server status |
|
|
187
|
+
| `mcp({ server: "name" })` | List tools from a server |
|
|
188
|
+
| `mcp({ search: "query" })` | Search tools (space-separated words OR'd) |
|
|
189
|
+
| `mcp({ describe: "tool_name" })` | Show tool parameters |
|
|
190
|
+
| `mcp({ tool: "name", args: '{}' })` | Call a tool (args is a JSON string) |
|
|
191
|
+
| `mcp({ connect: "name" })` | Force connect/reconnect a server |
|
|
192
|
+
| `/mcp` | Interactive panel (status, tools, reconnect) |
|
|
193
|
+
|
|
194
|
+
### Known Limitations
|
|
195
|
+
|
|
196
|
+
- **OAuth servers require `mcp-remote`**: The adapter doesn't implement the MCP OAuth browser flow natively. Use `mcp-remote` as a stdio proxy for OAuth servers.
|
|
197
|
+
- **Figma remote MCP** (`mcp.figma.com`): Blocks dynamic client registration — only whitelisted clients can connect via OAuth. Use Figma's desktop app local MCP server instead (`http://127.0.0.1:3845/mcp`), which requires Dev Mode (paid plan).
|
|
198
|
+
- **Metadata cache**: `pi-mcp-adapter` caches tool metadata to `~/.pi/agent/mcp-cache.json` (hardcoded path, doesn't affect functionality).
|
|
199
|
+
- **OAuth token storage**: `mcp-remote` stores tokens in `~/.mcp-auth/`, separate from Kata's config dir.
|
|
200
|
+
|
|
67
201
|
## The /kata Command
|
|
68
202
|
|
|
69
203
|
The main extension registers `/kata` with subcommands:
|
|
@@ -111,8 +245,9 @@ Kata uses `~/.kata-cli/` (not `~/.kata/`) to avoid collision with other Kata app
|
|
|
111
245
|
agents/ — Synced from src/resources/agents/
|
|
112
246
|
skills/ — Synced from src/resources/skills/
|
|
113
247
|
AGENTS.md — Synced from src/resources/AGENTS.md
|
|
248
|
+
mcp.json — MCP server configuration (scaffolded on first launch, never overwritten)
|
|
114
249
|
auth.json — API keys
|
|
115
|
-
settings.json — User settings
|
|
250
|
+
settings.json — User settings (includes packages: ["npm:pi-mcp-adapter"])
|
|
116
251
|
models.json — Custom model definitions
|
|
117
252
|
sessions/ — Session history
|
|
118
253
|
preferences.md — Global Kata preferences
|
|
@@ -130,6 +265,7 @@ Set by `loader.ts` before pi starts:
|
|
|
130
265
|
| `KATA_BIN_PATH` | Absolute path to loader, used by subagent |
|
|
131
266
|
| `KATA_WORKFLOW_PATH` | Absolute path to bundled KATA-WORKFLOW.md |
|
|
132
267
|
| `KATA_BUNDLED_EXTENSION_PATHS` | Colon-joined extension entry points for subagent |
|
|
268
|
+
| `KATA_MCP_CONFIG_PATH` | Absolute path to `~/.kata-cli/agent/mcp.json` |
|
|
133
269
|
|
|
134
270
|
## Development
|
|
135
271
|
|
|
@@ -143,7 +279,7 @@ npm run copy-themes
|
|
|
143
279
|
# Run
|
|
144
280
|
node dist/loader.js
|
|
145
281
|
|
|
146
|
-
# Test
|
|
282
|
+
# Test (37 tests: app smoke, resource sync, MCP integration, package validation)
|
|
147
283
|
npm test
|
|
148
284
|
```
|
|
149
285
|
|
package/dist/cli.js
CHANGED
|
@@ -36,10 +36,30 @@ if (!settingsManager.getQuietStartup()) {
|
|
|
36
36
|
if (!settingsManager.getCollapseChangelog()) {
|
|
37
37
|
settingsManager.setCollapseChangelog(true);
|
|
38
38
|
}
|
|
39
|
+
// Ensure pi-mcp-adapter is in the packages list so pi auto-installs it on startup.
|
|
40
|
+
// Bootstrap only when packages have never been configured. If users later remove the
|
|
41
|
+
// adapter from settings.json, that opt-out should persist.
|
|
42
|
+
const MCP_ADAPTER_PACKAGE = 'npm:pi-mcp-adapter';
|
|
43
|
+
const globalSettings = settingsManager.getGlobalSettings();
|
|
44
|
+
const globalPackages = [...(globalSettings.packages ?? [])];
|
|
45
|
+
const hasConfiguredPackages = Object.prototype.hasOwnProperty.call(globalSettings, "packages");
|
|
46
|
+
if (!hasConfiguredPackages && !globalPackages.includes(MCP_ADAPTER_PACKAGE)) {
|
|
47
|
+
settingsManager.setPackages([...globalPackages, MCP_ADAPTER_PACKAGE]);
|
|
48
|
+
}
|
|
49
|
+
await settingsManager.flush();
|
|
39
50
|
const sessionManager = SessionManager.create(process.cwd(), sessionsDir);
|
|
40
51
|
initResources(agentDir);
|
|
41
52
|
const resourceLoader = buildResourceLoader(agentDir);
|
|
42
53
|
await resourceLoader.reload();
|
|
54
|
+
// Inject --mcp-config flag value into the extension runtime.
|
|
55
|
+
// pi-mcp-adapter reads this via pi.getFlag("mcp-config") at session_start.
|
|
56
|
+
// Kata doesn't call pi's main() which does the two-pass argv parsing that
|
|
57
|
+
// normally populates flagValues, so we must do it manually here.
|
|
58
|
+
const mcpConfigPath = process.env.KATA_MCP_CONFIG_PATH;
|
|
59
|
+
if (mcpConfigPath) {
|
|
60
|
+
const extResult = resourceLoader.getExtensions();
|
|
61
|
+
extResult.runtime.flagValues.set('mcp-config', mcpConfigPath);
|
|
62
|
+
}
|
|
43
63
|
const { session, extensionsResult } = await createAgentSession({
|
|
44
64
|
authStorage,
|
|
45
65
|
modelRegistry,
|
package/dist/loader.js
CHANGED
|
@@ -91,5 +91,15 @@ process.env.KATA_BUNDLED_EXTENSION_PATHS = [
|
|
|
91
91
|
join(agentDir, "extensions", "ask-user-questions.ts"),
|
|
92
92
|
join(agentDir, "extensions", "get-secrets-from-user.ts"),
|
|
93
93
|
].join(":");
|
|
94
|
+
// KATA_MCP_CONFIG_PATH — absolute path to Kata's MCP config file.
|
|
95
|
+
// pi-mcp-adapter reads --mcp-config from process.argv directly (before session_start fires).
|
|
96
|
+
// We inject it here so the adapter uses ~/.kata-cli/agent/mcp.json instead of the
|
|
97
|
+
// default ~/.pi/agent/mcp.json.
|
|
98
|
+
const mcpConfigPath = join(agentDir, "mcp.json");
|
|
99
|
+
process.env.KATA_MCP_CONFIG_PATH = mcpConfigPath;
|
|
100
|
+
const hasMcpConfigArg = process.argv.some((arg) => arg === "--mcp-config" || arg.startsWith("--mcp-config="));
|
|
101
|
+
if (!hasMcpConfigArg) {
|
|
102
|
+
process.argv.push("--mcp-config", mcpConfigPath);
|
|
103
|
+
}
|
|
94
104
|
// Dynamic import defers ESM evaluation — config.js will see PI_PACKAGE_DIR above
|
|
95
105
|
await import("./cli.js");
|
package/dist/resource-loader.js
CHANGED
|
@@ -2,6 +2,19 @@ import { DefaultResourceLoader } from '@mariozechner/pi-coding-agent';
|
|
|
2
2
|
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
/**
|
|
6
|
+
* Starter mcp.json written to agentDir on first launch.
|
|
7
|
+
* Uses the `imports` field so users can pull in their existing Claude/Cursor config.
|
|
8
|
+
* mcpServers is intentionally empty — users add their own servers here.
|
|
9
|
+
*/
|
|
10
|
+
const STARTER_MCP_JSON = JSON.stringify({
|
|
11
|
+
imports: [],
|
|
12
|
+
settings: {
|
|
13
|
+
toolPrefix: 'server',
|
|
14
|
+
idleTimeout: 10,
|
|
15
|
+
},
|
|
16
|
+
mcpServers: {},
|
|
17
|
+
}, null, 2) + '\n';
|
|
5
18
|
// Resolves to the bundled src/resources/ inside the npm package at runtime:
|
|
6
19
|
// dist/resource-loader.js → .. → package root → src/resources/
|
|
7
20
|
const resourcesDir = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'src', 'resources');
|
|
@@ -39,6 +52,12 @@ export function initResources(agentDir) {
|
|
|
39
52
|
if (existsSync(srcAgentsMd)) {
|
|
40
53
|
writeFileSync(destAgentsMd, readFileSync(srcAgentsMd));
|
|
41
54
|
}
|
|
55
|
+
// Scaffold starter mcp.json — only if it doesn't exist yet.
|
|
56
|
+
// Never overwrite: preserve the user's MCP server configuration.
|
|
57
|
+
const mcpConfigPath = join(agentDir, 'mcp.json');
|
|
58
|
+
if (!existsSync(mcpConfigPath)) {
|
|
59
|
+
writeFileSync(mcpConfigPath, STARTER_MCP_JSON, 'utf-8');
|
|
60
|
+
}
|
|
42
61
|
}
|
|
43
62
|
/**
|
|
44
63
|
* Constructs a DefaultResourceLoader with no additionalExtensionPaths.
|
package/package.json
CHANGED
package/src/resources/AGENTS.md
CHANGED
|
@@ -9,7 +9,7 @@ Kata CLI is a thin wrapper around pi-coding-agent that provides:
|
|
|
9
9
|
- **Branded entry point**: `src/loader.ts` sets env vars and launches `src/cli.ts`
|
|
10
10
|
- **Bundled extensions**: `src/resources/extensions/` contains all built-in extensions
|
|
11
11
|
- **Resource syncing**: `src/resource-loader.ts` copies bundled extensions to `~/.kata-cli/agent/` on startup
|
|
12
|
-
- **Config directory**: `~/.kata-cli/` (not `~/.
|
|
12
|
+
- **Config directory**: `~/.kata-cli/` (not `~/.pi/` to avoid collision with other Kata apps)
|
|
13
13
|
- **Package shim**: `pkg/package.json` provides `piConfig` with `name: "kata"` and `configDir: ".kata-cli"`
|
|
14
14
|
|
|
15
15
|
## Directory Structure
|
|
@@ -18,7 +18,7 @@ Kata CLI is a thin wrapper around pi-coding-agent that provides:
|
|
|
18
18
|
apps/cli/
|
|
19
19
|
src/
|
|
20
20
|
loader.ts — Entry point, sets KATA_* env vars, imports cli.ts
|
|
21
|
-
cli.ts — Thin wrapper that calls
|
|
21
|
+
cli.ts — Thin wrapper that calls createAgentSession() + InteractiveMode
|
|
22
22
|
app-paths.ts — Exports appRoot, agentDir, sessionsDir, authFilePath
|
|
23
23
|
resource-loader.ts — Syncs bundled resources to ~/.kata-cli/agent/
|
|
24
24
|
wizard.ts — First-run setup, env key hydration
|
|
@@ -55,6 +55,7 @@ Kata sets these env vars in `loader.ts` before importing `cli.ts`:
|
|
|
55
55
|
| `KATA_BIN_PATH` | Absolute path to loader, used by subagent to spawn Kata |
|
|
56
56
|
| `KATA_WORKFLOW_PATH` | Absolute path to bundled KATA-WORKFLOW.md |
|
|
57
57
|
| `KATA_BUNDLED_EXTENSION_PATHS` | Colon-joined list of extension entry points |
|
|
58
|
+
| `KATA_MCP_CONFIG_PATH` | Absolute path to `~/.kata-cli/agent/mcp.json` (also injected as `--mcp-config` argv) |
|
|
58
59
|
|
|
59
60
|
## The /kata Command
|
|
60
61
|
|
|
@@ -99,6 +100,156 @@ Kata stores project state in `.kata/` at the project root:
|
|
|
99
100
|
- **Copy themes**: `npm run copy-themes` (copies theme assets from pi-coding-agent)
|
|
100
101
|
- **Dependencies**: Consumed via npm from `@mariozechner/pi-coding-agent` — never fork
|
|
101
102
|
|
|
103
|
+
## MCP Support
|
|
104
|
+
|
|
105
|
+
Kata ships with MCP (Model Context Protocol) support via [`pi-mcp-adapter`](https://github.com/nicobailon/pi-mcp-adapter), auto-installed on first launch. One proxy `mcp` tool (~200 tokens) gives the agent on-demand access to any MCP server's tools without burning context on tool definitions.
|
|
106
|
+
|
|
107
|
+
### How it works
|
|
108
|
+
|
|
109
|
+
There are three integration points that make MCP work in Kata:
|
|
110
|
+
|
|
111
|
+
1. **Package seeding** (`cli.ts`): Seeds `npm:pi-mcp-adapter` into `settingsManager.getPackages()` on every startup. Pi's package manager auto-installs it globally if missing.
|
|
112
|
+
|
|
113
|
+
2. **Config path injection** (`loader.ts` + `cli.ts`): Kata bypasses pi's `main()` and calls `createAgentSession()` directly, which means pi's two-pass argv parsing (that normally populates `runtime.flagValues`) never runs. Two things compensate:
|
|
114
|
+
- `loader.ts` pushes `--mcp-config ~/.kata-cli/agent/mcp.json` into `process.argv` — the adapter reads this at extension load time for `directTools` registration.
|
|
115
|
+
- `cli.ts` manually sets `runtime.flagValues.set('mcp-config', ...)` after `resourceLoader.reload()` — the adapter reads this via `pi.getFlag('mcp-config')` at `session_start` for the main initialization.
|
|
116
|
+
|
|
117
|
+
3. **Config scaffolding** (`resource-loader.ts`): Creates a starter `~/.kata-cli/agent/mcp.json` on first launch. Never overwrites existing config.
|
|
118
|
+
|
|
119
|
+
### Configuring MCP servers
|
|
120
|
+
|
|
121
|
+
Edit `~/.kata-cli/agent/mcp.json` to add servers. Servers can use **stdio** (local process) or **HTTP** (remote endpoint) transport.
|
|
122
|
+
|
|
123
|
+
#### Stdio servers (local process)
|
|
124
|
+
|
|
125
|
+
Most MCP servers run as a local process via `npx`:
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"mcpServers": {
|
|
130
|
+
"my-server": {
|
|
131
|
+
"command": "npx",
|
|
132
|
+
"args": ["-y", "some-mcp-server"],
|
|
133
|
+
"env": {
|
|
134
|
+
"API_KEY": "${MY_API_KEY}"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Environment variables support `${VAR}` interpolation from `process.env`.
|
|
142
|
+
|
|
143
|
+
#### HTTP servers with OAuth (e.g. Linear)
|
|
144
|
+
|
|
145
|
+
Many hosted MCP servers (Linear, Figma, etc.) use OAuth 2.1 authentication via the MCP spec. These require [`mcp-remote`](https://github.com/geelen/mcp-remote) as a stdio proxy that handles the OAuth browser flow:
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"mcpServers": {
|
|
150
|
+
"linear": {
|
|
151
|
+
"command": "npx",
|
|
152
|
+
"args": ["-y", "mcp-remote", "https://mcp.linear.app/mcp"]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
On first connection, `mcp-remote` opens a browser window for OAuth consent. Tokens are cached in `~/.mcp-auth/` for subsequent sessions.
|
|
159
|
+
|
|
160
|
+
**Linear MCP setup (complete example):**
|
|
161
|
+
|
|
162
|
+
1. Add the server to `~/.kata-cli/agent/mcp.json`:
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"settings": { "toolPrefix": "server", "idleTimeout": 10 },
|
|
166
|
+
"mcpServers": {
|
|
167
|
+
"linear": {
|
|
168
|
+
"command": "npx",
|
|
169
|
+
"args": ["-y", "mcp-remote", "https://mcp.linear.app/mcp"]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
2. Restart Kata.
|
|
176
|
+
|
|
177
|
+
3. Connect the server (triggers the OAuth flow in your browser):
|
|
178
|
+
```
|
|
179
|
+
mcp({ connect: "linear" })
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
4. Authorize Kata in the browser when prompted by Linear.
|
|
183
|
+
|
|
184
|
+
5. Use Linear tools:
|
|
185
|
+
```
|
|
186
|
+
mcp({ server: "linear" }) — list all Linear tools
|
|
187
|
+
mcp({ search: "issues" }) — search for issue-related tools
|
|
188
|
+
mcp({ tool: "linear_list_teams" }) — call a specific tool
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Troubleshooting OAuth:**
|
|
192
|
+
- If you see `internal server error`, clear cached auth: `rm -rf ~/.mcp-auth` and reconnect.
|
|
193
|
+
- Make sure you're running a recent version of Node.js.
|
|
194
|
+
- Use `/mcp` to check server status interactively.
|
|
195
|
+
|
|
196
|
+
#### HTTP servers with bearer token auth
|
|
197
|
+
|
|
198
|
+
For servers that accept API keys or personal access tokens:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"mcpServers": {
|
|
203
|
+
"my-api": {
|
|
204
|
+
"url": "https://api.example.com/mcp",
|
|
205
|
+
"auth": "bearer",
|
|
206
|
+
"bearerTokenEnv": "MY_API_KEY"
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### Importing existing configs
|
|
213
|
+
|
|
214
|
+
Pull in your existing Claude Code, Cursor, or VS Code MCP configuration:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"imports": ["claude-code", "cursor"],
|
|
219
|
+
"mcpServers": {}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Supported sources: `cursor`, `claude-code`, `claude-desktop`, `vscode`, `windsurf`, `codex`.
|
|
224
|
+
|
|
225
|
+
### Server lifecycle
|
|
226
|
+
|
|
227
|
+
| Mode | Behavior |
|
|
228
|
+
|------|----------|
|
|
229
|
+
| `lazy` (default) | Connect on first tool call. Disconnect after idle timeout. Cached metadata keeps search/list working offline. |
|
|
230
|
+
| `eager` | Connect at startup. No auto-reconnect on drop. |
|
|
231
|
+
| `keep-alive` | Connect at startup. Auto-reconnect via health checks. |
|
|
232
|
+
|
|
233
|
+
### Usage
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
mcp({ }) — show server status
|
|
237
|
+
mcp({ server: "linear" }) — list tools from a server
|
|
238
|
+
mcp({ search: "issues create" }) — search tools (space-separated words OR'd)
|
|
239
|
+
mcp({ describe: "linear_save_issue" }) — show tool parameters
|
|
240
|
+
mcp({ tool: "linear_list_teams" }) — call a tool (no args)
|
|
241
|
+
mcp({ tool: "linear_save_issue", args: '{"title": "Bug fix"}' }) — call with args (JSON string)
|
|
242
|
+
mcp({ connect: "linear" }) — force connect/reconnect a server
|
|
243
|
+
/mcp — interactive panel (status, tools, reconnect, OAuth)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Known limitations
|
|
247
|
+
|
|
248
|
+
- **OAuth servers require `mcp-remote`**: The adapter doesn't implement the MCP OAuth browser flow natively. Use `mcp-remote` as a stdio proxy for any server that requires OAuth (Linear, Figma remote, etc.).
|
|
249
|
+
- **Figma remote MCP (`mcp.figma.com`)**: Blocks dynamic client registration — only whitelisted clients (Cursor, Claude Code, VS Code) can connect via OAuth. Use the Figma desktop app's local MCP server instead (`http://127.0.0.1:3845/mcp`), which requires Figma desktop with Dev Mode (paid plan).
|
|
250
|
+
- **Metadata cache path**: `pi-mcp-adapter` caches tool metadata to `~/.pi/agent/mcp-cache.json` (hardcoded). This doesn't affect functionality — just means the cache lives outside Kata's config dir.
|
|
251
|
+
- **OAuth token storage**: `mcp-remote` stores tokens in `~/.mcp-auth/`, separate from Kata's config dir.
|
|
252
|
+
|
|
102
253
|
## Key Conventions
|
|
103
254
|
|
|
104
255
|
- All env var names use `KATA_` prefix (not `GSD_` or `PI_`)
|
|
@@ -106,3 +257,4 @@ Kata stores project state in `.kata/` at the project root:
|
|
|
106
257
|
- Extensions are synced from `src/resources/extensions/` to `~/.kata-cli/agent/extensions/` on every launch
|
|
107
258
|
- The `shared/` extension directory is a library, not an entry point — it's imported by other extensions
|
|
108
259
|
- Branch naming for workflow: `kata/M001/S01` (milestone/slice)
|
|
260
|
+
- MCP config lives at `~/.kata-cli/agent/mcp.json` (not `~/.pi/agent/mcp.json`)
|
|
@@ -398,8 +398,9 @@ function parseFrontmatterBlock(frontmatter: string): KataPreferences {
|
|
|
398
398
|
const valuePart = remainder.trim();
|
|
399
399
|
|
|
400
400
|
if (valuePart === "") {
|
|
401
|
-
const
|
|
402
|
-
|
|
401
|
+
const nextNonEmptyLine =
|
|
402
|
+
lines.slice(i + 1).find((candidate) => candidate.trim() !== "") ?? "";
|
|
403
|
+
const nextTrimmed = nextNonEmptyLine.trim();
|
|
403
404
|
if (nextTrimmed.startsWith("- ")) {
|
|
404
405
|
const items: unknown[] = [];
|
|
405
406
|
let j = i + 1;
|
|
@@ -481,9 +482,16 @@ function parseFrontmatterBlock(frontmatter: string): KataPreferences {
|
|
|
481
482
|
current[key] = items;
|
|
482
483
|
i = j - 1;
|
|
483
484
|
} else {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
485
|
+
// Check if the next non-empty line is actually indented deeper (a real nested block).
|
|
486
|
+
// If not, this key simply has no value — skip it rather than creating an empty object.
|
|
487
|
+
const nextIndent =
|
|
488
|
+
nextNonEmptyLine.match(/^\s*/)?.[0].length ?? indent;
|
|
489
|
+
if (nextIndent > indent) {
|
|
490
|
+
const obj: Record<string, unknown> = {};
|
|
491
|
+
current[key] = obj;
|
|
492
|
+
stack.push({ indent, value: obj });
|
|
493
|
+
}
|
|
494
|
+
// else: key with no value and no nested block — leave it undefined
|
|
487
495
|
}
|
|
488
496
|
continue;
|
|
489
497
|
}
|
|
@@ -494,9 +502,13 @@ function parseFrontmatterBlock(frontmatter: string): KataPreferences {
|
|
|
494
502
|
return root as KataPreferences;
|
|
495
503
|
}
|
|
496
504
|
|
|
497
|
-
function parseScalar(
|
|
505
|
+
function parseScalar(
|
|
506
|
+
value: string,
|
|
507
|
+
): string | number | boolean | unknown[] | Record<string, never> {
|
|
498
508
|
if (value === "true") return true;
|
|
499
509
|
if (value === "false") return false;
|
|
510
|
+
if (value === "[]") return [];
|
|
511
|
+
if (value === "{}") return {};
|
|
500
512
|
if (/^-?\d+$/.test(value)) return Number(value);
|
|
501
513
|
return value.replace(/^['\"]|['\"]$/g, "");
|
|
502
514
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const resolveTsHookPath = fileURLToPath(new URL('./resolve-ts.mjs', import.meta.url));
|
|
10
|
+
const preferencesPath = fileURLToPath(new URL('../preferences.ts', import.meta.url));
|
|
11
|
+
|
|
12
|
+
test('loadEffectiveKataPreferences preserves blank-line-separated skill_rules lists', () => {
|
|
13
|
+
const tmp = mkdtempSync(join(tmpdir(), 'kata-preferences-frontmatter-'));
|
|
14
|
+
const kataDir = join(tmp, '.kata');
|
|
15
|
+
mkdirSync(kataDir, { recursive: true });
|
|
16
|
+
writeFileSync(
|
|
17
|
+
join(kataDir, 'preferences.md'),
|
|
18
|
+
`---
|
|
19
|
+
skill_rules:
|
|
20
|
+
|
|
21
|
+
- when: build
|
|
22
|
+
use:
|
|
23
|
+
- test-driven-development
|
|
24
|
+
---
|
|
25
|
+
`,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const script = `
|
|
29
|
+
import { loadEffectiveKataPreferences } from ${JSON.stringify(preferencesPath)};
|
|
30
|
+
const prefs = loadEffectiveKataPreferences();
|
|
31
|
+
console.log(JSON.stringify(prefs?.preferences.skill_rules ?? null));
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const output = execFileSync(
|
|
35
|
+
'node',
|
|
36
|
+
['--import', resolveTsHookPath, '--experimental-strip-types', '-e', script],
|
|
37
|
+
{
|
|
38
|
+
cwd: tmp,
|
|
39
|
+
env: {
|
|
40
|
+
...process.env,
|
|
41
|
+
HOME: tmp,
|
|
42
|
+
},
|
|
43
|
+
encoding: 'utf-8',
|
|
44
|
+
},
|
|
45
|
+
).trim();
|
|
46
|
+
|
|
47
|
+
assert.deepEqual(JSON.parse(output), [
|
|
48
|
+
{
|
|
49
|
+
when: 'build',
|
|
50
|
+
use: ['test-driven-development'],
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
});
|