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
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
node_modules
*.log
node_modules/
*.log
.kiro/
.vscode/
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,25 @@ npm install -g @rkristelijn/lcode

# Or use with npx (no installation)
npx @rkristelijn/lcode
```

### MCP Integration (AI Assistants)

lcode provides native Model Context Protocol (MCP) support for AI assistants like Kiro CLI:

# Kiro CLI MCP Integration
kiro-cli mcp add --name "lcode" --command "npx" --args "@rkristelijn/lcode"
```bash
# After global installation, add to ~/.kiro/settings/mcp.json
{
"mcpServers": {
"lcode": {
"command": "lcode-mcp",
"args": []
}
}
}
```

📖 [Kiro CLI MCP Integration Guide](docs/mcp-integration.md)
📖 [Full MCP Integration Guide](docs/mcp-integration.md)

### Basic Usage

Expand Down
166 changes: 115 additions & 51 deletions docs/mcp-integration.md
Original file line number Diff line number Diff line change
@@ -1,96 +1,160 @@
# lcode MCP Integration for Kiro CLI

lcode provides native Model Context Protocol (MCP) support, allowing AI assistants like Kiro to discover and interact with your repositories through structured tools.

## Quick Setup

Add lcode as an MCP tool to Kiro CLI:
### Global Installation (Recommended)

Install lcode globally and configure the MCP server:

```bash
kiro-cli mcp add \
--name "lcode" \
--scope global \
--command "npx" \
--args "@rkristelijn/lcode"
# Install lcode globally
npm install -g @rkristelijn/lcode

# Add to Kiro CLI MCP configuration
# Create or edit ~/.kiro/settings/mcp.json
{
"mcpServers": {
"lcode": {
"command": "lcode-mcp",
"args": [],
"env": {}
}
}
}
```

Or manually add to `~/.kiro/settings/mcp.json`:
### Local Development Setup

```json
For local development or testing:

```bash
# Clone and install
git clone https://github.com/rkristelijn/lcode.git
cd lcode
npm install

# Configure MCP to use local server
# Edit .kiro/settings/mcp.json in your project
{
"mcpServers": {
"lcode": {
"command": "npx",
"args": ["@rkristelijn/lcode"],
"disabled": false
"command": "node",
"args": ["/path/to/lcode/mcp-server.mjs"],
"env": {}
}
}
}
```

## Usage with Kiro
## MCP Tools

Once configured, you can use lcode directly in Kiro chat:
The lcode MCP server exposes two tools:

```
"List all TypeScript repositories in ~/git/hub"
→ Uses: npx @rkristelijn/lcode ~/git/hub 2 --list --lang ts
### `list_repos`
Lists all git repositories with language detection and filtering.

"Show me all Python projects"
→ Uses: npx @rkristelijn/lcode ~ 3 --list --lang python
**Parameters:**
- `path` (optional) - Directory to search (default: from config)
- `maxDepth` (optional) - Search depth 1-10 (default: from config)
- `language` (optional) - Filter by language(s), comma-separated (e.g., "ts,js")

"Find Java or Kotlin projects"
→ Uses: npx @rkristelijn/lcode ~ 3 --list --lang java,kotlin
**Returns:** Array of repositories with index, name, path, languages, and description.

"Open the first TypeScript project in VS Code"
→ Uses: npx @rkristelijn/lcode ~ 3 --lang ts --select 0 "code ."
```
### `select_repo`
Gets detailed information about a specific repository.

## Available Commands
**Parameters:**
- `index` (optional) - Repository index from list_repos
- `name` (optional) - Repository name
- `path` (optional) - Search path (default: from config)
- `maxDepth` (optional) - Search depth (default: from config)

All lcode commands work through npx:
**Returns:** Repository details including full path and metadata.

```bash
# List repositories
npx @rkristelijn/lcode ~/git/hub 2 --list
## Usage with Kiro

# Filter by language
npx @rkristelijn/lcode ~/git/hub 2 --list --lang ts
Once configured, Kiro can use lcode tools naturally:

# Multiple languages
npx @rkristelijn/lcode ~/git/hub 2 --list --lang ts,js
```
User: "Find me 4 TypeScript repos"
Kiro: [Uses list_repos with language="ts"]

# Select and open
npx @rkristelijn/lcode ~/git/hub 2 --select 0 "code ."
User: "Show all Python projects"
Kiro: [Uses list_repos with language="python"]

# Help
npx @rkristelijn/lcode --help
User: "List Java or Kotlin projects"
Kiro: [Uses list_repos with language="java,kotlin"]

User: "Get details about the first repo"
Kiro: [Uses select_repo with index=0]
```

## Benefits

- ✅ No installation required (uses npx)
- ✅ Always uses latest version
- ✅ Works with all lcode features
- ✅ Native MCP protocol support
- ✅ Structured tool interface for AI assistants
- ✅ Language filtering built-in
- ✅ Fast repository discovery
- ✅ Fast repository discovery with caching
- ✅ Works with any MCP-compatible AI tool
- ✅ Type-safe tool definitions

## Example Workflows

### Find and open a project
### Find TypeScript projects
```
User: "Show me all my TypeScript projects"
Kiro: [Lists TypeScript repos with indices]
User: "Open the second one"
Kiro: [Executes: npx @rkristelijn/lcode --select 1 --lang ts]
User: "Find me TypeScript repositories"
→ Kiro calls: list_repos({ language: "ts" })
→ Returns: Structured list with indices, paths, and descriptions
```

### Language-specific search
### Multi-language search
```
User: "Find all Python projects in my home directory"
Kiro: [Executes: npx @rkristelijn/lcode ~ 3 --list --lang python]
User: "Show me Java or Kotlin projects"
→ Kiro calls: list_repos({ language: "java,kotlin" })
→ Returns: Filtered list of matching repositories
```

### Multi-language filtering
### Get repository details
```
User: "Show me Java or Kotlin projects"
Kiro: [Executes: npx @rkristelijn/lcode ~ 3 --list --lang java,kotlin]
User: "Tell me about repo #5"
→ Kiro calls: select_repo({ index: 5 })
→ Returns: Full details including path and metadata
```

## Configuration

lcode uses `~/.lcodeconfig` for default settings:

```json
{
"path": "~",
"maxDepth": 5,
"execute": "code .",
"execute2": "zsh"
}
```

The MCP server respects these defaults when parameters are not provided.

## Troubleshooting

### MCP server not loading
```bash
# Test the MCP server directly
node mcp-server.mjs

# Check Kiro MCP status
kiro-cli mcp list
```

### Tools not appearing
- Restart Kiro CLI after updating mcp.json
- Verify lcode-mcp is in your PATH (for global install)
- Check node version (requires Node.js 16+)

### Language filtering not working
- Use lowercase language codes: "ts", "python", "java"
- Multiple languages: "ts,js" (comma-separated, no spaces)
- Check supported languages in README.md
141 changes: 141 additions & 0 deletions mcp-server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { glob } from 'glob';
import path from 'path';
import fs from 'fs';
import { expandHomeDir, isGitRepo, detectLanguages, getReadmePreview } from './src/utils.mjs';
import { RepoCache } from './src/cache.mjs';

const cache = new RepoCache();
const configPath = path.resolve(process.env.HOME, '.lcodeconfig');

const getConfig = () => {
try {
if (fs.existsSync(configPath)) {
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
}
} catch {}

Check failure on line 19 in mcp-server.mjs

View workflow job for this annotation

GitHub Actions / test (22)

Empty block statement

Check failure on line 19 in mcp-server.mjs

View workflow job for this annotation

GitHub Actions / test (24)

Empty block statement

Check failure on line 19 in mcp-server.mjs

View workflow job for this annotation

GitHub Actions / test (20)

Empty block statement

Check failure on line 19 in mcp-server.mjs

View workflow job for this annotation

GitHub Actions / test (16)

Empty block statement
return { path: '~', maxDepth: 5 };
};

const findRepos = async (searchPath, maxDepth, langFilter = null) => {
const expanded = expandHomeDir(searchPath);
const cacheKey = `${expanded}-${maxDepth}`;

let repos = cache.get(cacheKey);
if (!repos) {
const pattern = `${expanded}/${'*/'.repeat(maxDepth - 1)}`;
const folders = await glob(pattern, {
ignore: ['**/node_modules/**', '**/build/**', '**/dist/**', '**/.git/**'],
absolute: true
});

repos = folders
.filter(isGitRepo)
.map(repoPath => ({
path: repoPath,
name: path.basename(repoPath),
languages: detectLanguages(repoPath),
description: getReadmePreview(repoPath)
}));

cache.set(cacheKey, repos);
}

if (langFilter) {
const filters = langFilter.split(',').map(l => l.trim().toLowerCase());
repos = repos.filter(r => r.languages.some(l => filters.includes(l.toLowerCase())));
}

return repos;
};

const server = new Server(
{ name: 'lcode', version: '1.0.0' },
{ capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'list_repos',
description: 'List all git repositories in a directory with language detection',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'Directory to search (default: from config)' },
maxDepth: { type: 'number', description: 'Search depth 1-10 (default: from config)' },
language: { type: 'string', description: 'Filter by language (comma-separated: ts,js,python,etc)' }
}
}
},
{
name: 'select_repo',
description: 'Get details about a specific repository by index or name',
inputSchema: {
type: 'object',
properties: {
index: { type: 'number', description: 'Repository index from list_repos' },
name: { type: 'string', description: 'Repository name' },
path: { type: 'string', description: 'Search path (default: from config)' },
maxDepth: { type: 'number', description: 'Search depth (default: from config)' }
}
}
}
]
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const config = getConfig();

if (name === 'list_repos') {
const searchPath = args.path || config.path || '~';
const maxDepth = args.maxDepth || config.maxDepth || 5;
const repos = await findRepos(searchPath, maxDepth, args.language);

return {
content: [{
type: 'text',
text: JSON.stringify(repos.map((r, i) => ({
index: i,
name: r.name,
path: r.path,
languages: r.languages,
description: r.description
})), null, 2)
}]
};
}

if (name === 'select_repo') {
const searchPath = args.path || config.path || '~';
const maxDepth = args.maxDepth || config.maxDepth || 5;
const repos = await findRepos(searchPath, maxDepth);

let repo;
if (args.index !== undefined) {
repo = repos[args.index];
} else if (args.name) {
repo = repos.find(r => r.name === args.name);
}

if (!repo) {
return { content: [{ type: 'text', text: 'Repository not found' }], isError: true };
}

return {
content: [{
type: 'text',
text: JSON.stringify(repo, null, 2)
}]
};
}

return { content: [{ type: 'text', text: 'Unknown tool' }], isError: true };
});

const transport = new StdioServerTransport();
await server.connect(transport);
Loading
Loading