feat(mcp): add colapp CLI, read-only MCP server, and project skill#6
Conversation
Introduce Colanode Cursor skill, extended AGENTS.md, docs/mcp-skills.md, and @colanode/mcp-server with read-only repo tools for documentation and client operation discovery. Co-authored-by: Cursor <cursoragent@cursor.com>
Introduce @colanode/cli with colapp mcp, skills install, and doctor commands; refactor MCP server startup for reuse and wire .cursor/mcp.json to npx colapp mcp. Co-authored-by: Cursor <cursoragent@cursor.com>
Updated the skills installation process to link skills for both Cursor and VS Code Copilot. Added checks for VS Code configuration in the doctor command, ensuring proper setup for user and workspace environments. Enhanced documentation for skills installation and VS Code configuration. Co-authored-by: Cursor <cursoragent@cursor.com>
…tion Introduced the `colapp install` command for linking the CLI globally or locally, enhancing user experience. Updated the MCP server startup command in `.cursor/mcp.json` to use the CLI directly. Enhanced documentation to reflect these changes and improve clarity on installation and usage. Co-authored-by: Cursor <cursoragent@cursor.com>
Expand mcp-skills.md with quick start, commands, allowlist, IDE configs, and troubleshooting; sync AGENTS.md, skill, roadmap, and README. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Code Review
This pull request introduces AI-assisted development tools to the Colanode monorepo, adding a new CLI package (@colanode/cli / colapp) and a read-only MCP server (@colanode/mcp-server) to assist AI agents with repository documentation, queries, and mutations. The review feedback focuses on critical bootstrapping and cross-platform compatibility issues. Key recommendations include replacing static imports of unbuilt packages with dynamic imports to prevent startup crashes on fresh clones, resolving hardcoded macOS paths for VS Code configurations, handling Windows-specific path separators and symbolic link permissions, and updating regex patterns to support both single and double quotes for robust parsing.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| import { findRepoRoot } from '@colanode/mcp-server/repo'; | ||
|
|
||
| export const getRepoRoot = (cwd = process.cwd()) => findRepoRoot(cwd); |
There was a problem hiding this comment.
Statically importing findRepoRoot from @colanode/mcp-server/repo creates a circular bootstrapping dependency. On a fresh clone, running node packages/cli/dist/cli.js install will crash immediately because @colanode/mcp-server has not been built yet, preventing the CLI from resolving the import. Implementing a lightweight, self-contained findRepoRoot directly in the CLI package resolves this bootstrap issue.
export const findRepoRoot = (startDir = process.cwd()): string => {
const envRoot = process.env.COLANODE_REPO_ROOT;
if (envRoot && existsSync(join(envRoot, 'package.json'))) {
return resolve(envRoot);
}
let dir = resolve(startDir);
let current = dir;
while (current !== dirname(current)) {
if (
existsSync(join(current, 'package.json')) &&
existsSync(join(current, 'packages', 'client'))
) {
return current;
}
current = dirname(current);
}
return dir;
};
export const getRepoRoot = (cwd = process.cwd()) => findRepoRoot(cwd);| import { existsSync } from 'node:fs'; | ||
|
|
||
| import { startColanodeMcpServer } from '@colanode/mcp-server/server'; | ||
|
|
||
| import { getMcpServerEntry, getRepoRoot } from '../lib/paths.js'; |
There was a problem hiding this comment.
Statically importing startColanodeMcpServer from @colanode/mcp-server/server will cause the CLI to crash on startup if the MCP server has not been built yet. Removing this static import and using a dynamic import inside runMcp ensures that ensureMcpServerBuilt can run and build the package first.
| import { existsSync } from 'node:fs'; | |
| import { startColanodeMcpServer } from '@colanode/mcp-server/server'; | |
| import { getMcpServerEntry, getRepoRoot } from '../lib/paths.js'; | |
| import { existsSync } from 'node:fs'; | |
| import { getMcpServerEntry, getRepoRoot } from '../lib/paths.js'; |
| ensureMcpServerBuilt(repoRoot); | ||
| await startColanodeMcpServer(repoRoot); | ||
| }; |
There was a problem hiding this comment.
| const result = spawnSync(command, args, { | ||
| cwd: options.cwd, | ||
| stdio: options.stdio ?? 'inherit', | ||
| env: process.env, | ||
| }); |
There was a problem hiding this comment.
On Windows, spawnSync will fail to execute npm directly because it is a batch file (npm.cmd), not a direct executable. Enabling shell: true on Windows ensures cross-platform compatibility.
| const result = spawnSync(command, args, { | |
| cwd: options.cwd, | |
| stdio: options.stdio ?? 'inherit', | |
| env: process.env, | |
| }); | |
| const result = spawnSync(command, args, { | |
| cwd: options.cwd, | |
| stdio: options.stdio ?? 'inherit', | |
| env: process.env, | |
| shell: process.platform === 'win32', | |
| }); |
| const userPath = join( | ||
| homedir(), | ||
| 'Library', | ||
| 'Application Support', | ||
| 'Code', | ||
| 'User', | ||
| 'mcp.json' | ||
| ); |
There was a problem hiding this comment.
The path to the VS Code user mcp.json configuration is currently hardcoded to macOS. This will cause colapp doctor to fail on Windows and Linux. Resolving the path based on process.platform ensures cross-platform compatibility.
| const userPath = join( | |
| homedir(), | |
| 'Library', | |
| 'Application Support', | |
| 'Code', | |
| 'User', | |
| 'mcp.json' | |
| ); | |
| let userPath = ''; | |
| if (process.platform === 'win32') { | |
| userPath = join(process.env.APPDATA ?? join(homedir(), 'AppData', 'Roaming'), 'Code', 'User', 'mcp.json'); | |
| } else if (process.platform === 'darwin') { | |
| userPath = join(homedir(), 'Library', 'Application Support', 'Code', 'User', 'mcp.json'); | |
| } else { | |
| userPath = join(homedir(), '.config', 'Code', 'User', 'mcp.json'); | |
| } |
| const usesColapp = | ||
| raw.includes('colapp') || | ||
| raw.includes('packages/cli/dist/cli.js'); | ||
| const referencesRepo = raw.includes(repoRoot); |
There was a problem hiding this comment.
On Windows, paths in mcp.json may use forward slashes / or escaped backslashes \\. Comparing raw.includes(repoRoot) directly can fail due to path separator mismatches. Normalizing the separators to forward slashes before comparison fixes this.
| const referencesRepo = raw.includes(repoRoot); | |
| const referencesRepo = raw.replace(/\\/g, '/').includes(repoRoot.replace(/\\/g, '/')); |
| symlinkSync(linkSource, target, 'dir'); | ||
| console.log(`[colapp] Linked skill ${linkSource} -> ${target}`); |
There was a problem hiding this comment.
On Windows, creating symbolic links via symlinkSync requires administrator privileges or Developer Mode to be enabled. Wrapping this call in a try-catch block prevents the CLI from crashing and allows you to provide a helpful warning message to the user.
try {
symlinkSync(linkSource, target, 'dir');
console.log(`[colapp] Linked skill ${linkSource} -> ${target}`);
} catch (error) {
console.error(`[colapp] Failed to create symbolic link at ${target}:`, error);
console.error('[colapp] On Windows, you may need to run this command as Administrator or enable Developer Mode.');
}| repoRoot: string, | ||
| relativePath: string | ||
| ): string => { | ||
| const normalized = relativePath.replace(/^\/+/, ''); |
There was a problem hiding this comment.
| } | ||
|
|
||
| const content = readFileSync(filePath, 'utf8'); | ||
| const matches = content.matchAll(/'([^']+)':\s*new/g); |
There was a problem hiding this comment.
The current regular expression only matches single quotes for handler registration. If double quotes are used (e.g., due to Prettier or ESLint formatting), the parser will fail to extract the operations. Updating the regex to support both single and double quotes makes it much more robust.
| const matches = content.matchAll(/'([^']+)':\s*new/g); | |
| const matches = content.matchAll(/['\"]([^'\"]+)['\"]\\s*:\\s*new/g); |
Summary
@colanode/mcp-serverwith read-only MCP tools: list/search docs, read allowlisted source files, catalog client queries and mutations@colanode/cli(colapp):install,doctor,mcp,skills installfor local setup and IDE integration.cursor/skills/colanodewith VS Code Copilot symlink at.github/skills/colanode.cursor/mcp.json; document VS Code user/workspace MCP config indocs/mcp-skills.mdAGENTS.md,README.md, anddocs/roadmap.mdTest plan
npm run test -w @colanode/mcp-servernpm run test -w @colanode/clicolapp doctor— all checks passcolanodeserver starts; tools (colanode_list_docs,colanode_list_mutations, etc.) respondmcp.json; confirm colanode skill in Chat: Configure Skillscolapp installon a fresh clone putscolappon PATHMade with Cursor