codeboard 0.1.0__tar.gz → 0.2.0__tar.gz
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.
- {codeboard-0.1.0 → codeboard-0.2.0}/PKG-INFO +32 -1
- {codeboard-0.1.0 → codeboard-0.2.0}/README.md +31 -0
- {codeboard-0.1.0 → codeboard-0.2.0}/codeboard.egg-info/PKG-INFO +32 -1
- {codeboard-0.1.0 → codeboard-0.2.0}/codeboard.py +284 -1
- {codeboard-0.1.0 → codeboard-0.2.0}/pyproject.toml +1 -1
- {codeboard-0.1.0 → codeboard-0.2.0}/LICENSE +0 -0
- {codeboard-0.1.0 → codeboard-0.2.0}/codeboard.egg-info/SOURCES.txt +0 -0
- {codeboard-0.1.0 → codeboard-0.2.0}/codeboard.egg-info/dependency_links.txt +0 -0
- {codeboard-0.1.0 → codeboard-0.2.0}/codeboard.egg-info/entry_points.txt +0 -0
- {codeboard-0.1.0 → codeboard-0.2.0}/codeboard.egg-info/requires.txt +0 -0
- {codeboard-0.1.0 → codeboard-0.2.0}/codeboard.egg-info/top_level.txt +0 -0
- {codeboard-0.1.0 → codeboard-0.2.0}/setup.cfg +0 -0
- {codeboard-0.1.0 → codeboard-0.2.0}/tests/test_codeboard.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeboard
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Git repository dashboard for your local codebase
|
|
5
5
|
Author: Shaoyi Yang
|
|
6
6
|
License-Expression: MIT
|
|
@@ -212,6 +212,37 @@ cb --filter simona health # also works
|
|
|
212
212
|
| `cb graph <repo> [action]` | Code graph analysis | [gitnexus](https://github.com/nicolo-ribaudo/gitnexus) |
|
|
213
213
|
| `cb config` | Show or generate config file | — |
|
|
214
214
|
| `cb completions [bash\|zsh\|fish]` | Generate shell completion script | — |
|
|
215
|
+
| `cb mcp` | Run as MCP server for AI assistants | — |
|
|
216
|
+
|
|
217
|
+
## MCP Server (AI Integration)
|
|
218
|
+
|
|
219
|
+
CodeBoard can run as an [MCP](https://modelcontextprotocol.io/) server, letting AI assistants (Claude Code, Cursor, etc.) query your repo status directly.
|
|
220
|
+
|
|
221
|
+
Add to your Claude Code MCP config (`~/.claude/claude_desktop_config.json`):
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"mcpServers": {
|
|
226
|
+
"codeboard": {
|
|
227
|
+
"command": "cb",
|
|
228
|
+
"args": ["mcp"]
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Available tools:
|
|
235
|
+
|
|
236
|
+
| Tool | Description |
|
|
237
|
+
|------|-------------|
|
|
238
|
+
| `list_repos` | Quick list of all repos with name, branch, dirty status |
|
|
239
|
+
| `repo_status` | Full dashboard data (branch, commits, language, remote) |
|
|
240
|
+
| `health_check` | Categorized health report (uncommitted, unpushed, inactive) |
|
|
241
|
+
| `recent_activity` | Cross-repo commit timeline |
|
|
242
|
+
| `repo_detail` | Deep dive into a single repo |
|
|
243
|
+
| `search_code` | Search code across all repos (regex) |
|
|
244
|
+
|
|
245
|
+
No extra dependencies — uses stdio JSON-RPC 2.0, zero config.
|
|
215
246
|
|
|
216
247
|
## Shell Completion
|
|
217
248
|
|
|
@@ -186,6 +186,37 @@ cb --filter simona health # also works
|
|
|
186
186
|
| `cb graph <repo> [action]` | Code graph analysis | [gitnexus](https://github.com/nicolo-ribaudo/gitnexus) |
|
|
187
187
|
| `cb config` | Show or generate config file | — |
|
|
188
188
|
| `cb completions [bash\|zsh\|fish]` | Generate shell completion script | — |
|
|
189
|
+
| `cb mcp` | Run as MCP server for AI assistants | — |
|
|
190
|
+
|
|
191
|
+
## MCP Server (AI Integration)
|
|
192
|
+
|
|
193
|
+
CodeBoard can run as an [MCP](https://modelcontextprotocol.io/) server, letting AI assistants (Claude Code, Cursor, etc.) query your repo status directly.
|
|
194
|
+
|
|
195
|
+
Add to your Claude Code MCP config (`~/.claude/claude_desktop_config.json`):
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"mcpServers": {
|
|
200
|
+
"codeboard": {
|
|
201
|
+
"command": "cb",
|
|
202
|
+
"args": ["mcp"]
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Available tools:
|
|
209
|
+
|
|
210
|
+
| Tool | Description |
|
|
211
|
+
|------|-------------|
|
|
212
|
+
| `list_repos` | Quick list of all repos with name, branch, dirty status |
|
|
213
|
+
| `repo_status` | Full dashboard data (branch, commits, language, remote) |
|
|
214
|
+
| `health_check` | Categorized health report (uncommitted, unpushed, inactive) |
|
|
215
|
+
| `recent_activity` | Cross-repo commit timeline |
|
|
216
|
+
| `repo_detail` | Deep dive into a single repo |
|
|
217
|
+
| `search_code` | Search code across all repos (regex) |
|
|
218
|
+
|
|
219
|
+
No extra dependencies — uses stdio JSON-RPC 2.0, zero config.
|
|
189
220
|
|
|
190
221
|
## Shell Completion
|
|
191
222
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeboard
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Git repository dashboard for your local codebase
|
|
5
5
|
Author: Shaoyi Yang
|
|
6
6
|
License-Expression: MIT
|
|
@@ -212,6 +212,37 @@ cb --filter simona health # also works
|
|
|
212
212
|
| `cb graph <repo> [action]` | Code graph analysis | [gitnexus](https://github.com/nicolo-ribaudo/gitnexus) |
|
|
213
213
|
| `cb config` | Show or generate config file | — |
|
|
214
214
|
| `cb completions [bash\|zsh\|fish]` | Generate shell completion script | — |
|
|
215
|
+
| `cb mcp` | Run as MCP server for AI assistants | — |
|
|
216
|
+
|
|
217
|
+
## MCP Server (AI Integration)
|
|
218
|
+
|
|
219
|
+
CodeBoard can run as an [MCP](https://modelcontextprotocol.io/) server, letting AI assistants (Claude Code, Cursor, etc.) query your repo status directly.
|
|
220
|
+
|
|
221
|
+
Add to your Claude Code MCP config (`~/.claude/claude_desktop_config.json`):
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"mcpServers": {
|
|
226
|
+
"codeboard": {
|
|
227
|
+
"command": "cb",
|
|
228
|
+
"args": ["mcp"]
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Available tools:
|
|
235
|
+
|
|
236
|
+
| Tool | Description |
|
|
237
|
+
|------|-------------|
|
|
238
|
+
| `list_repos` | Quick list of all repos with name, branch, dirty status |
|
|
239
|
+
| `repo_status` | Full dashboard data (branch, commits, language, remote) |
|
|
240
|
+
| `health_check` | Categorized health report (uncommitted, unpushed, inactive) |
|
|
241
|
+
| `recent_activity` | Cross-repo commit timeline |
|
|
242
|
+
| `repo_detail` | Deep dive into a single repo |
|
|
243
|
+
| `search_code` | Search code across all repos (regex) |
|
|
244
|
+
|
|
245
|
+
No extra dependencies — uses stdio JSON-RPC 2.0, zero config.
|
|
215
246
|
|
|
216
247
|
## Shell Completion
|
|
217
248
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""CodeBoard — Git repository dashboard for your local codebase."""
|
|
3
3
|
|
|
4
|
-
__version__ = "0.
|
|
4
|
+
__version__ = "0.2.0"
|
|
5
5
|
|
|
6
6
|
import argparse
|
|
7
7
|
import json
|
|
@@ -3056,6 +3056,286 @@ def cmd_config(args):
|
|
|
3056
3056
|
console.print(f"[green]Generated default config: {CONFIG_FILE}[/green]")
|
|
3057
3057
|
|
|
3058
3058
|
|
|
3059
|
+
# ---------------------------------------------------------------------------
|
|
3060
|
+
# MCP Server (Model Context Protocol) — stdio JSON-RPC 2.0
|
|
3061
|
+
# ---------------------------------------------------------------------------
|
|
3062
|
+
|
|
3063
|
+
_MCP_TOOLS = [
|
|
3064
|
+
{
|
|
3065
|
+
"name": "list_repos",
|
|
3066
|
+
"description": "List all git repositories with basic status (name, path, branch, dirty file count, remote type). Fast, lightweight scan.",
|
|
3067
|
+
"inputSchema": {
|
|
3068
|
+
"type": "object",
|
|
3069
|
+
"properties": {
|
|
3070
|
+
"filter": {"type": "string", "description": "Filter repos by name keyword"},
|
|
3071
|
+
},
|
|
3072
|
+
},
|
|
3073
|
+
},
|
|
3074
|
+
{
|
|
3075
|
+
"name": "repo_status",
|
|
3076
|
+
"description": "Full dashboard: all repos with branch, last commit time, dirty count, commit count, language, remote URL, ahead/behind counts. Sorted by recent activity.",
|
|
3077
|
+
"inputSchema": {
|
|
3078
|
+
"type": "object",
|
|
3079
|
+
"properties": {
|
|
3080
|
+
"filter": {"type": "string", "description": "Filter repos by name keyword"},
|
|
3081
|
+
"sort": {"type": "string", "enum": ["activity", "name", "commits", "changes"], "description": "Sort order (default: activity)"},
|
|
3082
|
+
},
|
|
3083
|
+
},
|
|
3084
|
+
},
|
|
3085
|
+
{
|
|
3086
|
+
"name": "health_check",
|
|
3087
|
+
"description": "Health report: categorizes repos into uncommitted changes, unpushed commits, behind remote, no remote, inactive >30d, and clean. Great for finding repos that need attention.",
|
|
3088
|
+
"inputSchema": {
|
|
3089
|
+
"type": "object",
|
|
3090
|
+
"properties": {
|
|
3091
|
+
"filter": {"type": "string", "description": "Filter repos by name keyword"},
|
|
3092
|
+
},
|
|
3093
|
+
},
|
|
3094
|
+
},
|
|
3095
|
+
{
|
|
3096
|
+
"name": "recent_activity",
|
|
3097
|
+
"description": "Cross-repo commit timeline: recent commits across all repos, sorted by time. Shows repo, author, message, and relative time.",
|
|
3098
|
+
"inputSchema": {
|
|
3099
|
+
"type": "object",
|
|
3100
|
+
"properties": {
|
|
3101
|
+
"filter": {"type": "string", "description": "Filter repos by name keyword"},
|
|
3102
|
+
"limit": {"type": "integer", "description": "Max number of commits (default: 30)"},
|
|
3103
|
+
},
|
|
3104
|
+
},
|
|
3105
|
+
},
|
|
3106
|
+
{
|
|
3107
|
+
"name": "repo_detail",
|
|
3108
|
+
"description": "Deep dive into a single repo: languages, contributors, tags, recent commits, branch list, remote info.",
|
|
3109
|
+
"inputSchema": {
|
|
3110
|
+
"type": "object",
|
|
3111
|
+
"properties": {
|
|
3112
|
+
"repo": {"type": "string", "description": "Repository name (fuzzy matched)"},
|
|
3113
|
+
},
|
|
3114
|
+
"required": ["repo"],
|
|
3115
|
+
},
|
|
3116
|
+
},
|
|
3117
|
+
{
|
|
3118
|
+
"name": "search_code",
|
|
3119
|
+
"description": "Search code across all repos using git grep (regex). Returns matching file paths and line content per repo.",
|
|
3120
|
+
"inputSchema": {
|
|
3121
|
+
"type": "object",
|
|
3122
|
+
"properties": {
|
|
3123
|
+
"pattern": {"type": "string", "description": "Search pattern (regex)"},
|
|
3124
|
+
"filter": {"type": "string", "description": "Filter repos by name keyword"},
|
|
3125
|
+
},
|
|
3126
|
+
"required": ["pattern"],
|
|
3127
|
+
},
|
|
3128
|
+
},
|
|
3129
|
+
]
|
|
3130
|
+
|
|
3131
|
+
|
|
3132
|
+
def _mcp_handle_tool(name: str, arguments: dict, code_dir: Path) -> str:
|
|
3133
|
+
"""Execute an MCP tool and return JSON string result."""
|
|
3134
|
+
|
|
3135
|
+
if name == "list_repos":
|
|
3136
|
+
repos = scan_all(code_dir, full=False, filter_kw=arguments.get("filter", ""))
|
|
3137
|
+
data = [
|
|
3138
|
+
{"name": r["name"], "path": r["path"], "branch": r["branch"],
|
|
3139
|
+
"dirty": r["dirty"], "remote_type": r["remote_type"],
|
|
3140
|
+
"last_commit": r["last_time_rel"]}
|
|
3141
|
+
for r in sort_repos(repos, "activity")
|
|
3142
|
+
]
|
|
3143
|
+
return json.dumps(data, ensure_ascii=False)
|
|
3144
|
+
|
|
3145
|
+
if name == "repo_status":
|
|
3146
|
+
repos = scan_all(code_dir, full=True, filter_kw=arguments.get("filter", ""))
|
|
3147
|
+
repos = sort_repos(repos, arguments.get("sort", "activity"))
|
|
3148
|
+
for r in repos:
|
|
3149
|
+
r.pop("last_time", None)
|
|
3150
|
+
return json.dumps(repos, ensure_ascii=False)
|
|
3151
|
+
|
|
3152
|
+
if name == "health_check":
|
|
3153
|
+
repos = scan_all(code_dir, full=False, filter_kw=arguments.get("filter", ""))
|
|
3154
|
+
now_ts = datetime.now(timezone.utc).timestamp()
|
|
3155
|
+
categories = {
|
|
3156
|
+
"uncommitted": [],
|
|
3157
|
+
"unpushed": [],
|
|
3158
|
+
"behind": [],
|
|
3159
|
+
"no_remote": [],
|
|
3160
|
+
"inactive_30d": [],
|
|
3161
|
+
"clean": [],
|
|
3162
|
+
}
|
|
3163
|
+
for r in repos:
|
|
3164
|
+
if r["dirty"] > 0:
|
|
3165
|
+
categories["uncommitted"].append({"name": r["name"], "dirty": r["dirty"]})
|
|
3166
|
+
if r["ahead"] > 0:
|
|
3167
|
+
categories["unpushed"].append({"name": r["name"], "ahead": r["ahead"]})
|
|
3168
|
+
if r["behind"] > 0:
|
|
3169
|
+
categories["behind"].append({"name": r["name"], "behind": r["behind"]})
|
|
3170
|
+
if r["remote_type"] == "none":
|
|
3171
|
+
categories["no_remote"].append(r["name"])
|
|
3172
|
+
elif r["last_time_ts"] and (now_ts - r["last_time_ts"]) > 30 * 86400:
|
|
3173
|
+
categories["inactive_30d"].append(r["name"])
|
|
3174
|
+
if r["dirty"] == 0 and r["ahead"] == 0:
|
|
3175
|
+
categories["clean"].append(r["name"])
|
|
3176
|
+
summary = {k: {"count": len(v), "repos": v} for k, v in categories.items()}
|
|
3177
|
+
summary["total_repos"] = len(repos)
|
|
3178
|
+
return json.dumps(summary, ensure_ascii=False)
|
|
3179
|
+
|
|
3180
|
+
if name == "recent_activity":
|
|
3181
|
+
filter_kw = arguments.get("filter", "")
|
|
3182
|
+
limit = arguments.get("limit", 30)
|
|
3183
|
+
repos = list_git_repos(code_dir, filter_kw)
|
|
3184
|
+
all_commits = []
|
|
3185
|
+
|
|
3186
|
+
def _get_commits(repo_path):
|
|
3187
|
+
output = run_git(repo_path, "log", "--all", f"--max-count={limit}", "--format=%aI|%an|%s")
|
|
3188
|
+
if not output:
|
|
3189
|
+
return []
|
|
3190
|
+
entries = []
|
|
3191
|
+
for line in output.split("\n"):
|
|
3192
|
+
if "|" not in line:
|
|
3193
|
+
continue
|
|
3194
|
+
parts = line.split("|", 2)
|
|
3195
|
+
if len(parts) < 3:
|
|
3196
|
+
continue
|
|
3197
|
+
try:
|
|
3198
|
+
dt = datetime.fromisoformat(parts[0])
|
|
3199
|
+
except ValueError:
|
|
3200
|
+
continue
|
|
3201
|
+
entries.append({
|
|
3202
|
+
"time": parts[0], "time_rel": relative_time(dt),
|
|
3203
|
+
"author": parts[1], "message": parts[2],
|
|
3204
|
+
"repo": repo_path.name,
|
|
3205
|
+
})
|
|
3206
|
+
return entries
|
|
3207
|
+
|
|
3208
|
+
with ThreadPoolExecutor(max_workers=8) as pool:
|
|
3209
|
+
for future in as_completed({pool.submit(_get_commits, r): r for r in repos}):
|
|
3210
|
+
all_commits.extend(future.result())
|
|
3211
|
+
|
|
3212
|
+
all_commits.sort(key=lambda c: c["time"], reverse=True)
|
|
3213
|
+
return json.dumps(all_commits[:limit], ensure_ascii=False)
|
|
3214
|
+
|
|
3215
|
+
if name == "repo_detail":
|
|
3216
|
+
repo_name = arguments.get("repo", "")
|
|
3217
|
+
repo_path = find_repo(code_dir, repo_name)
|
|
3218
|
+
if not repo_path:
|
|
3219
|
+
return json.dumps({"error": f"Repository not found: {repo_name}"})
|
|
3220
|
+
info = scan_repo(repo_path, full=True)
|
|
3221
|
+
if not info:
|
|
3222
|
+
return json.dumps({"error": f"Failed to scan: {repo_name}"})
|
|
3223
|
+
info.pop("last_time", None)
|
|
3224
|
+
# Add extra detail
|
|
3225
|
+
branches = run_git(repo_path, "branch", "--list", "--no-color")
|
|
3226
|
+
info["branches"] = [b.strip().lstrip("* ") for b in branches.split("\n") if b.strip()] if branches else []
|
|
3227
|
+
tags = run_git(repo_path, "tag", "--list", "--sort=-creatordate")
|
|
3228
|
+
info["tags"] = [t.strip() for t in tags.split("\n") if t.strip()][:10] if tags else []
|
|
3229
|
+
recent = run_git(repo_path, "log", "--max-count=10", "--format=%aI|%an|%s")
|
|
3230
|
+
info["recent_commits"] = []
|
|
3231
|
+
if recent:
|
|
3232
|
+
for line in recent.split("\n"):
|
|
3233
|
+
parts = line.split("|", 2)
|
|
3234
|
+
if len(parts) >= 3:
|
|
3235
|
+
info["recent_commits"].append({"time": parts[0], "author": parts[1], "message": parts[2]})
|
|
3236
|
+
return json.dumps(info, ensure_ascii=False)
|
|
3237
|
+
|
|
3238
|
+
if name == "search_code":
|
|
3239
|
+
pattern = arguments.get("pattern", "")
|
|
3240
|
+
if not pattern:
|
|
3241
|
+
return json.dumps({"error": "pattern is required"})
|
|
3242
|
+
filter_kw = arguments.get("filter", "")
|
|
3243
|
+
repos = list_git_repos(code_dir, filter_kw)
|
|
3244
|
+
results = {}
|
|
3245
|
+
|
|
3246
|
+
def _search(repo_path):
|
|
3247
|
+
try:
|
|
3248
|
+
r = subprocess.run(
|
|
3249
|
+
["git", "-C", str(repo_path), "grep", "-n", "--color=never", "-I", pattern],
|
|
3250
|
+
capture_output=True, text=True, timeout=15,
|
|
3251
|
+
)
|
|
3252
|
+
if r.returncode == 0 and r.stdout.strip():
|
|
3253
|
+
return (repo_path.name, r.stdout.strip())
|
|
3254
|
+
except subprocess.TimeoutExpired:
|
|
3255
|
+
pass
|
|
3256
|
+
return None
|
|
3257
|
+
|
|
3258
|
+
with ThreadPoolExecutor(max_workers=8) as pool:
|
|
3259
|
+
for future in as_completed({pool.submit(_search, r): r for r in repos}):
|
|
3260
|
+
result = future.result()
|
|
3261
|
+
if result:
|
|
3262
|
+
repo_name, output = result
|
|
3263
|
+
matches = []
|
|
3264
|
+
for line in output.split("\n")[:50]: # cap at 50 per repo
|
|
3265
|
+
parts = line.split(":", 2)
|
|
3266
|
+
if len(parts) >= 3:
|
|
3267
|
+
matches.append({"file": parts[0], "line": parts[1], "content": parts[2].strip()})
|
|
3268
|
+
else:
|
|
3269
|
+
matches.append({"raw": line})
|
|
3270
|
+
results[repo_name] = matches
|
|
3271
|
+
|
|
3272
|
+
return json.dumps(results, ensure_ascii=False)
|
|
3273
|
+
|
|
3274
|
+
return json.dumps({"error": f"Unknown tool: {name}"})
|
|
3275
|
+
|
|
3276
|
+
|
|
3277
|
+
def cmd_mcp(args):
|
|
3278
|
+
"""Run as MCP server over stdio (JSON-RPC 2.0)."""
|
|
3279
|
+
code_dir = Path(args.path).expanduser()
|
|
3280
|
+
|
|
3281
|
+
def _respond(msg_id, result):
|
|
3282
|
+
resp = {"jsonrpc": "2.0", "id": msg_id, "result": result}
|
|
3283
|
+
sys.stdout.write(json.dumps(resp) + "\n")
|
|
3284
|
+
sys.stdout.flush()
|
|
3285
|
+
|
|
3286
|
+
def _error(msg_id, code, message):
|
|
3287
|
+
resp = {"jsonrpc": "2.0", "id": msg_id, "error": {"code": code, "message": message}}
|
|
3288
|
+
sys.stdout.write(json.dumps(resp) + "\n")
|
|
3289
|
+
sys.stdout.flush()
|
|
3290
|
+
|
|
3291
|
+
for line in sys.stdin:
|
|
3292
|
+
line = line.strip()
|
|
3293
|
+
if not line:
|
|
3294
|
+
continue
|
|
3295
|
+
try:
|
|
3296
|
+
msg = json.loads(line)
|
|
3297
|
+
except json.JSONDecodeError:
|
|
3298
|
+
continue
|
|
3299
|
+
|
|
3300
|
+
method = msg.get("method")
|
|
3301
|
+
msg_id = msg.get("id")
|
|
3302
|
+
params = msg.get("params", {})
|
|
3303
|
+
|
|
3304
|
+
# Notifications (no id) — just acknowledge silently
|
|
3305
|
+
if msg_id is None:
|
|
3306
|
+
continue
|
|
3307
|
+
|
|
3308
|
+
if method == "initialize":
|
|
3309
|
+
_respond(msg_id, {
|
|
3310
|
+
"protocolVersion": "2024-11-05",
|
|
3311
|
+
"capabilities": {"tools": {}},
|
|
3312
|
+
"serverInfo": {"name": "codeboard", "version": __version__},
|
|
3313
|
+
})
|
|
3314
|
+
|
|
3315
|
+
elif method == "ping":
|
|
3316
|
+
_respond(msg_id, {})
|
|
3317
|
+
|
|
3318
|
+
elif method == "tools/list":
|
|
3319
|
+
_respond(msg_id, {"tools": _MCP_TOOLS})
|
|
3320
|
+
|
|
3321
|
+
elif method == "tools/call":
|
|
3322
|
+
tool_name = params.get("name", "")
|
|
3323
|
+
tool_args = params.get("arguments", {})
|
|
3324
|
+
try:
|
|
3325
|
+
result_text = _mcp_handle_tool(tool_name, tool_args, code_dir)
|
|
3326
|
+
_respond(msg_id, {
|
|
3327
|
+
"content": [{"type": "text", "text": result_text}],
|
|
3328
|
+
})
|
|
3329
|
+
except Exception as e:
|
|
3330
|
+
_respond(msg_id, {
|
|
3331
|
+
"content": [{"type": "text", "text": f"Error: {e}"}],
|
|
3332
|
+
"isError": True,
|
|
3333
|
+
})
|
|
3334
|
+
|
|
3335
|
+
else:
|
|
3336
|
+
_error(msg_id, -32601, f"Method not found: {method}")
|
|
3337
|
+
|
|
3338
|
+
|
|
3059
3339
|
def main():
|
|
3060
3340
|
global _ui_lang, console
|
|
3061
3341
|
|
|
@@ -3124,6 +3404,8 @@ def main():
|
|
|
3124
3404
|
comp_parser = sub.add_parser("completions", help="Generate shell completion script")
|
|
3125
3405
|
comp_parser.add_argument("shell", nargs="?", default="bash", choices=["bash", "zsh", "fish"], help="Shell type (default: bash)")
|
|
3126
3406
|
|
|
3407
|
+
sub.add_parser("mcp", help="Run as MCP server (stdio, for AI assistants)")
|
|
3408
|
+
|
|
3127
3409
|
args = parser.parse_args()
|
|
3128
3410
|
|
|
3129
3411
|
# Apply --lang and --no-color
|
|
@@ -3152,6 +3434,7 @@ def main():
|
|
|
3152
3434
|
"graph": cmd_graph,
|
|
3153
3435
|
"config": cmd_config,
|
|
3154
3436
|
"completions": cmd_completions,
|
|
3437
|
+
"mcp": cmd_mcp,
|
|
3155
3438
|
}
|
|
3156
3439
|
|
|
3157
3440
|
handler = commands.get(cmd)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|