@hunyed15/codecgc 0.1.2 → 0.1.3

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 CHANGED
@@ -52,12 +52,34 @@ cgc-install --mode user
52
52
  - `~/.claude/hooks/route-edit.ps1`
53
53
  - `~/.claude/commands/cgc*.md` 自定义 slash commands
54
54
 
55
+ 当前安装链路还会注册 3 个 MCP server:
56
+
57
+ - `codecgc`:CodeCGC 编排器 MCP
58
+ - `codex`:后端执行器 MCP
59
+ - `gemini`:前端执行器 MCP
60
+
55
61
  安装完成后,可以在 Claude 中直接使用:
56
62
 
57
63
  - `/cgc`
58
64
  - `/cgc-install`
59
65
  - `/cgc-status`
60
66
  - `/cgc-doctor`
67
+ - `/cgc-plan`
68
+ - `/cgc-build`
69
+ - `/cgc-fix`
70
+ - `/cgc-test`
71
+ - `/cgc-review`
72
+ - `/cgc-route`
73
+ - `/cgc-history`
74
+ - `/cgc-package-audit`
75
+ - `/cgc-external-audit`
76
+ - `/cgc-release-readiness`
77
+ - `/cgc-lifecycle`
78
+
79
+ 当前这些 Claude commands 已调整为:
80
+
81
+ - 优先走 `codecgc` MCP orchestrator
82
+ - 只有在 MCP tool 路径不可用时,才回退到本地 CLI
61
83
 
62
84
  如果安装时 Python 尚未就绪,自动集成会跳过,此时可在安装 Python 后手动执行 `cgc-install --mode user`。
63
85
 
@@ -0,0 +1,17 @@
1
+ # CodeCGC MCP Server
2
+
3
+ This package contains the first minimal CodeCGC orchestrator MCP server.
4
+
5
+ Current scope:
6
+
7
+ - install
8
+ - status
9
+ - doctor
10
+ - entry
11
+ - continue
12
+ - explain
13
+ - review
14
+ - history
15
+ - route
16
+
17
+ This server currently reuses the existing CodeCGC runtime scripts rather than replacing them.
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "codecgcmcp"
7
+ version = "0.1.0"
8
+ description = "FastMCP orchestrator server for CodeCGC."
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ dependencies = [
12
+ "mcp[cli]>=1.21.2",
13
+ ]
14
+
15
+ [project.scripts]
16
+ codecgcmcp = "codecgcmcp.cli:main"
17
+
18
+ [tool.hatch.build.targets.wheel]
19
+ packages = ["src/codecgcmcp"]
@@ -0,0 +1 @@
1
+ """CodeCGC MCP orchestrator package."""
@@ -0,0 +1,12 @@
1
+ """Console entry point for the CodeCGC MCP server."""
2
+
3
+ from codecgcmcp.server import run
4
+
5
+
6
+ def main() -> None:
7
+ """Start the CodeCGC MCP server."""
8
+ run()
9
+
10
+
11
+ if __name__ == "__main__":
12
+ main()
@@ -0,0 +1,525 @@
1
+ """FastMCP server implementation for the CodeCGC orchestrator."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Annotated, Any, Literal
8
+
9
+ from mcp.server.fastmcp import FastMCP
10
+ from mcp.types import CallToolResult
11
+ from mcp.types import TextContent
12
+ from pydantic import Field
13
+
14
+ from codecgc_workflow_runtime import run_json_script
15
+
16
+ mcp = FastMCP("CodeCGC MCP Server")
17
+
18
+
19
+ def _append_repeated_flag(args: list[str], flag: str, values: list[str]) -> None:
20
+ for value in values:
21
+ text = str(value).strip()
22
+ if text:
23
+ args.extend([flag, text])
24
+
25
+
26
+ def _normalize_workspace(workspace: str) -> str:
27
+ return str(Path(workspace).expanduser().resolve()) if str(workspace).strip() else ""
28
+
29
+
30
+ def _as_tool_result(payload: dict[str, Any]) -> CallToolResult:
31
+ return CallToolResult(
32
+ content=[
33
+ TextContent(
34
+ type="text",
35
+ text=json.dumps(payload, ensure_ascii=True, indent=2),
36
+ )
37
+ ],
38
+ isError=not bool(payload.get("success", True)),
39
+ )
40
+
41
+
42
+ @mcp.tool(
43
+ name="codecgc.install",
44
+ description="Install or sync CodeCGC integration for the current project or Claude user profile.",
45
+ meta={"version": "0.1.0", "author": "CodeCGC"},
46
+ )
47
+ async def codecgc_install(
48
+ mode: Annotated[
49
+ Literal["local", "user-dry-run", "user", "status", "doctor"],
50
+ Field(description="Install mode for CodeCGC integration."),
51
+ ] = "local",
52
+ format: Annotated[
53
+ Literal["summary", "json"],
54
+ Field(description="Output format. Use summary for normal product-facing replies."),
55
+ ] = "summary",
56
+ workspace: Annotated[
57
+ str,
58
+ Field(description="Optional target workspace root for local/status/doctor modes."),
59
+ ] = "",
60
+ user_root: Annotated[
61
+ str,
62
+ Field(description="Optional explicit Claude user root for user/user-dry-run modes."),
63
+ ] = "",
64
+ ) -> CallToolResult:
65
+ args = ["--mode", mode, "--format", format]
66
+ normalized_workspace = _normalize_workspace(workspace)
67
+ if normalized_workspace:
68
+ args.extend(["--workspace", normalized_workspace])
69
+ if str(user_root).strip():
70
+ args.extend(["--user-root", str(user_root).strip()])
71
+ return _as_tool_result(run_json_script("install_codecgc.py", *args))
72
+
73
+
74
+ @mcp.tool(
75
+ name="codecgc.status",
76
+ description="Check CodeCGC integration readiness for the current or specified workspace.",
77
+ meta={"version": "0.1.0", "author": "CodeCGC"},
78
+ )
79
+ async def codecgc_status(
80
+ workspace: Annotated[
81
+ str,
82
+ Field(description="Optional target workspace root."),
83
+ ] = "",
84
+ ) -> CallToolResult:
85
+ args = ["--mode", "status", "--format", "json"]
86
+ normalized_workspace = _normalize_workspace(workspace)
87
+ if normalized_workspace:
88
+ args.extend(["--workspace", normalized_workspace])
89
+ return _as_tool_result(run_json_script("install_codecgc.py", *args))
90
+
91
+
92
+ @mcp.tool(
93
+ name="codecgc.doctor",
94
+ description="Run CodeCGC runtime and integration health checks.",
95
+ meta={"version": "0.1.0", "author": "CodeCGC"},
96
+ )
97
+ async def codecgc_doctor(
98
+ workspace: Annotated[
99
+ str,
100
+ Field(description="Optional target workspace root."),
101
+ ] = "",
102
+ ) -> CallToolResult:
103
+ args = ["--mode", "doctor", "--format", "json"]
104
+ normalized_workspace = _normalize_workspace(workspace)
105
+ if normalized_workspace:
106
+ args.extend(["--workspace", normalized_workspace])
107
+ return _as_tool_result(run_json_script("install_codecgc.py", *args))
108
+
109
+
110
+ @mcp.tool(
111
+ name="codecgc.entry",
112
+ description="Primary CodeCGC orchestration entry for new requests, continue, explain, and auto-dispatch decisions.",
113
+ meta={"version": "0.1.0", "author": "CodeCGC"},
114
+ )
115
+ async def codecgc_entry(
116
+ request: Annotated[str, "Natural-language request for CodeCGC."] = "",
117
+ mode: Annotated[
118
+ Literal["auto", "new", "continue", "explain"],
119
+ Field(description="Entry mode."),
120
+ ] = "auto",
121
+ flow: Annotated[
122
+ Literal["", "feature", "issue"],
123
+ Field(description="Optional explicit workflow flow."),
124
+ ] = "",
125
+ slug: Annotated[str, "Optional workflow slug."] = "",
126
+ latest: Annotated[bool, "Use the latest matching workflow."] = False,
127
+ include_fixtures: Annotated[bool, "Allow fixture workflows to be considered."] = False,
128
+ auto_dispatch: Annotated[bool, "Allow entry to dispatch when the route permits it."] = False,
129
+ dry_run: Annotated[bool, "Avoid real executor dispatch."] = False,
130
+ audit_file: Annotated[str, "Optional audit file for review dispatch."] = "",
131
+ decision: Annotated[
132
+ Literal["", "accepted", "changes-requested"],
133
+ Field(description="Optional review decision when the request is review-like."),
134
+ ] = "",
135
+ ) -> CallToolResult:
136
+ args = ["--mode", mode]
137
+ if str(request).strip():
138
+ args.extend(["--request", str(request).strip()])
139
+ if flow:
140
+ args.extend(["--flow", flow])
141
+ if str(slug).strip():
142
+ args.extend(["--slug", str(slug).strip()])
143
+ if latest:
144
+ args.append("--latest")
145
+ if include_fixtures:
146
+ args.append("--include-fixtures")
147
+ if auto_dispatch:
148
+ args.append("--auto-dispatch")
149
+ if dry_run:
150
+ args.append("--dry-run")
151
+ if str(audit_file).strip():
152
+ args.extend(["--audit-file", str(audit_file).strip()])
153
+ if decision:
154
+ args.extend(["--decision", decision])
155
+ return _as_tool_result(run_json_script("entry_codecgc_workflow.py", *args))
156
+
157
+
158
+ @mcp.tool(
159
+ name="codecgc.continue",
160
+ description="Continue the current or latest CodeCGC workflow using the existing runtime state.",
161
+ meta={"version": "0.1.0", "author": "CodeCGC"},
162
+ )
163
+ async def codecgc_continue(
164
+ request: Annotated[str, "Optional continue request text. Defaults to continuing the current work."] = "继续刚刚的工作",
165
+ flow: Annotated[
166
+ Literal["", "feature", "issue"],
167
+ Field(description="Optional explicit workflow flow."),
168
+ ] = "",
169
+ slug: Annotated[str, "Optional workflow slug."] = "",
170
+ latest: Annotated[bool, "Use the latest matching workflow."] = True,
171
+ include_fixtures: Annotated[bool, "Allow fixture workflows to be considered."] = False,
172
+ auto_dispatch: Annotated[bool, "Allow continue to dispatch when the route permits it."] = False,
173
+ dry_run: Annotated[bool, "Avoid real executor dispatch."] = False,
174
+ ) -> CallToolResult:
175
+ args = ["--mode", "continue", "--request", str(request).strip() or "继续刚刚的工作"]
176
+ if flow:
177
+ args.extend(["--flow", flow])
178
+ if str(slug).strip():
179
+ args.extend(["--slug", str(slug).strip()])
180
+ if latest:
181
+ args.append("--latest")
182
+ if include_fixtures:
183
+ args.append("--include-fixtures")
184
+ if auto_dispatch:
185
+ args.append("--auto-dispatch")
186
+ if dry_run:
187
+ args.append("--dry-run")
188
+ return _as_tool_result(run_json_script("entry_codecgc_workflow.py", *args))
189
+
190
+
191
+ @mcp.tool(
192
+ name="codecgc.explain",
193
+ description="Explain the current or latest CodeCGC workflow state and recommended next action.",
194
+ meta={"version": "0.1.0", "author": "CodeCGC"},
195
+ )
196
+ async def codecgc_explain(
197
+ request: Annotated[str, "Optional explain request text."] = "现在下一步该做什么",
198
+ flow: Annotated[
199
+ Literal["", "feature", "issue"],
200
+ Field(description="Optional explicit workflow flow."),
201
+ ] = "",
202
+ slug: Annotated[str, "Optional workflow slug."] = "",
203
+ latest: Annotated[bool, "Use the latest matching workflow."] = True,
204
+ include_fixtures: Annotated[bool, "Allow fixture workflows to be considered."] = False,
205
+ ) -> CallToolResult:
206
+ args = ["--mode", "explain", "--request", str(request).strip() or "现在下一步该做什么"]
207
+ if flow:
208
+ args.extend(["--flow", flow])
209
+ if str(slug).strip():
210
+ args.extend(["--slug", str(slug).strip()])
211
+ if latest:
212
+ args.append("--latest")
213
+ if include_fixtures:
214
+ args.append("--include-fixtures")
215
+ return _as_tool_result(run_json_script("entry_codecgc_workflow.py", *args))
216
+
217
+
218
+ @mcp.tool(
219
+ name="codecgc.review",
220
+ description="Review a CodeCGC execution audit and write the decision back to workflow artifacts.",
221
+ meta={"version": "0.1.0", "author": "CodeCGC"},
222
+ )
223
+ async def codecgc_review(
224
+ audit_file: Annotated[str, "Execution audit JSON file path."],
225
+ decision: Annotated[
226
+ Literal["accepted", "changes-requested"],
227
+ Field(description="Requested review decision."),
228
+ ],
229
+ risk: Annotated[list[str], Field(description="Optional remaining risks to record.")] = [],
230
+ next_step: Annotated[str, "Optional next-step text to record."] = "",
231
+ force: Annotated[bool, "Allow overwriting an existing review writeback."] = False,
232
+ ) -> CallToolResult:
233
+ args = ["--audit-file", str(audit_file).strip(), "--decision", decision]
234
+ _append_repeated_flag(args, "--risk", risk)
235
+ if str(next_step).strip():
236
+ args.extend(["--next-step", str(next_step).strip()])
237
+ if force:
238
+ args.append("--force")
239
+ return _as_tool_result(run_json_script("review_codecgc_workflow.py", *args))
240
+
241
+
242
+ @mcp.tool(
243
+ name="codecgc.history",
244
+ description="Read recent CodeCGC workflow history across feature and issue flows.",
245
+ meta={"version": "0.1.0", "author": "CodeCGC"},
246
+ )
247
+ async def codecgc_history(
248
+ flow: Annotated[
249
+ Literal["all", "feature", "issue"],
250
+ Field(description="Workflow flow filter."),
251
+ ] = "all",
252
+ status: Annotated[str, "Workflow status filter."] = "all",
253
+ last: Annotated[int, "Maximum number of records to return."] = 10,
254
+ include_fixtures: Annotated[bool, "Include fixture workflows."] = False,
255
+ ) -> CallToolResult:
256
+ args = ["--flow", flow, "--status", str(status).strip() or "all", "--last", str(int(last)), "--format", "json"]
257
+ if include_fixtures:
258
+ args.append("--include-fixtures")
259
+ return _as_tool_result(run_json_script("audit_codecgc_workflow_history.py", *args))
260
+
261
+
262
+ @mcp.tool(
263
+ name="codecgc.route",
264
+ description="Route an existing CodeCGC workflow to the recommended next command.",
265
+ meta={"version": "0.1.0", "author": "CodeCGC"},
266
+ )
267
+ async def codecgc_route(
268
+ flow: Annotated[
269
+ Literal["feature", "issue"],
270
+ Field(description="Workflow flow."),
271
+ ],
272
+ slug: Annotated[str, "Workflow slug."],
273
+ ) -> CallToolResult:
274
+ return _as_tool_result(run_json_script("route_codecgc_workflow.py", "--flow", flow, "--slug", str(slug).strip()))
275
+
276
+
277
+ @mcp.tool(
278
+ name="codecgc.plan",
279
+ description="Plan or repair a CodeCGC feature or issue workflow scaffold.",
280
+ meta={"version": "0.1.0", "author": "CodeCGC"},
281
+ )
282
+ async def codecgc_plan(
283
+ flow: Annotated[
284
+ Literal["feature", "issue"],
285
+ Field(description="Workflow flow."),
286
+ ],
287
+ slug: Annotated[str, "Workflow slug."],
288
+ summary: Annotated[str, "Planning summary."],
289
+ date: Annotated[str, "Optional workflow date, defaults to current date in runtime."] = "",
290
+ target_paths: Annotated[list[str], Field(description="Target paths for this workflow.")] = [],
291
+ kind: Annotated[
292
+ Literal["auto", "frontend", "backend"],
293
+ Field(description="Routing kind."),
294
+ ] = "auto",
295
+ goal: Annotated[str, "Optional goal text."] = "",
296
+ context: Annotated[list[str], Field(description="Optional context notes.")] = [],
297
+ user_story: Annotated[str, "Optional user story."] = "",
298
+ in_scope: Annotated[list[str], Field(description="In-scope items.")] = [],
299
+ out_of_scope: Annotated[list[str], Field(description="Out-of-scope items.")] = [],
300
+ acceptance: Annotated[list[str], Field(description="Acceptance criteria.")] = [],
301
+ risk: Annotated[list[str], Field(description="Risk items.")] = [],
302
+ dependency: Annotated[list[str], Field(description="Dependencies.")] = [],
303
+ assumption: Annotated[list[str], Field(description="Assumptions.")] = [],
304
+ open_question: Annotated[list[str], Field(description="Open questions.")] = [],
305
+ validation: Annotated[list[str], Field(description="Validation steps.")] = [],
306
+ rollback: Annotated[list[str], Field(description="Rollback notes.")] = [],
307
+ symptom: Annotated[str, "Issue symptom."] = "",
308
+ reproduction: Annotated[str, "Issue reproduction."] = "",
309
+ expected: Annotated[str, "Expected behavior."] = "",
310
+ actual: Annotated[str, "Actual behavior."] = "",
311
+ root_cause: Annotated[str, "Root cause note."] = "",
312
+ preferred_fix: Annotated[str, "Preferred fix note."] = "",
313
+ rejected_fix: Annotated[str, "Rejected fix note."] = "",
314
+ artifact_class: Annotated[
315
+ Literal["product", "fixture"],
316
+ Field(description="Artifact class."),
317
+ ] = "product",
318
+ force: Annotated[bool, "Allow overwriting scaffold state when needed."] = False,
319
+ ) -> CallToolResult:
320
+ args = ["--flow", flow, "--slug", str(slug).strip(), "--summary", str(summary).strip()]
321
+ if str(date).strip():
322
+ args.extend(["--date", str(date).strip()])
323
+ _append_repeated_flag(args, "--target-path", target_paths)
324
+ if kind:
325
+ args.extend(["--kind", kind])
326
+ if str(goal).strip():
327
+ args.extend(["--goal", str(goal).strip()])
328
+ _append_repeated_flag(args, "--context", context)
329
+ if str(user_story).strip():
330
+ args.extend(["--user-story", str(user_story).strip()])
331
+ _append_repeated_flag(args, "--in-scope", in_scope)
332
+ _append_repeated_flag(args, "--out-of-scope", out_of_scope)
333
+ _append_repeated_flag(args, "--acceptance", acceptance)
334
+ _append_repeated_flag(args, "--risk", risk)
335
+ _append_repeated_flag(args, "--dependency", dependency)
336
+ _append_repeated_flag(args, "--assumption", assumption)
337
+ _append_repeated_flag(args, "--open-question", open_question)
338
+ _append_repeated_flag(args, "--validation", validation)
339
+ _append_repeated_flag(args, "--rollback", rollback)
340
+ if str(symptom).strip():
341
+ args.extend(["--symptom", str(symptom).strip()])
342
+ if str(reproduction).strip():
343
+ args.extend(["--reproduction", str(reproduction).strip()])
344
+ if str(expected).strip():
345
+ args.extend(["--expected", str(expected).strip()])
346
+ if str(actual).strip():
347
+ args.extend(["--actual", str(actual).strip()])
348
+ if str(root_cause).strip():
349
+ args.extend(["--root-cause", str(root_cause).strip()])
350
+ if str(preferred_fix).strip():
351
+ args.extend(["--preferred-fix", str(preferred_fix).strip()])
352
+ if str(rejected_fix).strip():
353
+ args.extend(["--rejected-fix", str(rejected_fix).strip()])
354
+ args.extend(["--artifact-class", artifact_class])
355
+ if force:
356
+ args.append("--force")
357
+ return _as_tool_result(run_json_script("plan_codecgc_workflow.py", *args))
358
+
359
+
360
+ @mcp.tool(
361
+ name="codecgc.build",
362
+ description="Execute a CodeCGC feature step through the existing runtime.",
363
+ meta={"version": "0.1.0", "author": "CodeCGC"},
364
+ )
365
+ async def codecgc_build(
366
+ slug: Annotated[str, "Feature workflow slug."],
367
+ step_number: Annotated[int | None, "Optional explicit step number."] = None,
368
+ checklist_file: Annotated[str, "Optional checklist file path."] = "",
369
+ audit_root: Annotated[str, "Optional audit root path."] = "",
370
+ timeout_seconds: Annotated[int, "Execution timeout in seconds."] = 120,
371
+ session_id: Annotated[str, "Optional reusable executor session id."] = "",
372
+ dry_run: Annotated[bool, "Avoid real executor dispatch."] = False,
373
+ return_all_messages: Annotated[bool, "Return executor event logs when available."] = False,
374
+ ) -> CallToolResult:
375
+ args = ["--slug", str(slug).strip(), "--timeout-seconds", str(int(timeout_seconds))]
376
+ if step_number is not None:
377
+ args.extend(["--step-number", str(int(step_number))])
378
+ if str(checklist_file).strip():
379
+ args.extend(["--checklist-file", str(checklist_file).strip()])
380
+ if str(audit_root).strip():
381
+ args.extend(["--audit-root", str(audit_root).strip()])
382
+ if str(session_id).strip():
383
+ args.extend(["--session-id", str(session_id).strip()])
384
+ if dry_run:
385
+ args.append("--dry-run")
386
+ if return_all_messages:
387
+ args.append("--return-all-messages")
388
+ return _as_tool_result(run_json_script("run_codecgc_build.py", *args))
389
+
390
+
391
+ @mcp.tool(
392
+ name="codecgc.fix",
393
+ description="Execute a CodeCGC issue fix step through the existing runtime.",
394
+ meta={"version": "0.1.0", "author": "CodeCGC"},
395
+ )
396
+ async def codecgc_fix(
397
+ slug: Annotated[str, "Issue workflow slug."],
398
+ step_number: Annotated[int | None, "Optional explicit step number."] = None,
399
+ checklist_file: Annotated[str, "Optional checklist file path."] = "",
400
+ audit_root: Annotated[str, "Optional audit root path."] = "",
401
+ timeout_seconds: Annotated[int, "Execution timeout in seconds."] = 120,
402
+ session_id: Annotated[str, "Optional reusable executor session id."] = "",
403
+ dry_run: Annotated[bool, "Avoid real executor dispatch."] = False,
404
+ return_all_messages: Annotated[bool, "Return executor event logs when available."] = False,
405
+ ) -> CallToolResult:
406
+ args = ["--slug", str(slug).strip(), "--timeout-seconds", str(int(timeout_seconds))]
407
+ if step_number is not None:
408
+ args.extend(["--step-number", str(int(step_number))])
409
+ if str(checklist_file).strip():
410
+ args.extend(["--checklist-file", str(checklist_file).strip()])
411
+ if str(audit_root).strip():
412
+ args.extend(["--audit-root", str(audit_root).strip()])
413
+ if str(session_id).strip():
414
+ args.extend(["--session-id", str(session_id).strip()])
415
+ if dry_run:
416
+ args.append("--dry-run")
417
+ if return_all_messages:
418
+ args.append("--return-all-messages")
419
+ return _as_tool_result(run_json_script("run_codecgc_fix.py", *args))
420
+
421
+
422
+ @mcp.tool(
423
+ name="codecgc.test",
424
+ description="Execute a CodeCGC test step through the existing runtime.",
425
+ meta={"version": "0.1.0", "author": "CodeCGC"},
426
+ )
427
+ async def codecgc_test(
428
+ flow: Annotated[
429
+ Literal["feature", "issue"],
430
+ Field(description="Workflow flow."),
431
+ ],
432
+ slug: Annotated[str, "Workflow slug."],
433
+ step_number: Annotated[int | None, "Optional explicit step number."] = None,
434
+ checklist_file: Annotated[str, "Optional checklist file path."] = "",
435
+ audit_root: Annotated[str, "Optional audit root path."] = "",
436
+ timeout_seconds: Annotated[int, "Execution timeout in seconds."] = 120,
437
+ session_id: Annotated[str, "Optional reusable executor session id."] = "",
438
+ dry_run: Annotated[bool, "Avoid real executor dispatch."] = False,
439
+ return_all_messages: Annotated[bool, "Return executor event logs when available."] = False,
440
+ ) -> CallToolResult:
441
+ args = ["--flow", flow, "--slug", str(slug).strip(), "--timeout-seconds", str(int(timeout_seconds))]
442
+ if step_number is not None:
443
+ args.extend(["--step-number", str(int(step_number))])
444
+ if str(checklist_file).strip():
445
+ args.extend(["--checklist-file", str(checklist_file).strip()])
446
+ if str(audit_root).strip():
447
+ args.extend(["--audit-root", str(audit_root).strip()])
448
+ if str(session_id).strip():
449
+ args.extend(["--session-id", str(session_id).strip()])
450
+ if dry_run:
451
+ args.append("--dry-run")
452
+ if return_all_messages:
453
+ args.append("--return-all-messages")
454
+ return _as_tool_result(run_json_script("run_codecgc_test.py", *args))
455
+
456
+
457
+ @mcp.tool(
458
+ name="codecgc.package_audit",
459
+ description="Audit whether the published CodeCGC package contains all required runtime files.",
460
+ meta={"version": "0.1.0", "author": "CodeCGC"},
461
+ )
462
+ async def codecgc_package_audit(
463
+ format: Annotated[
464
+ Literal["summary", "json"],
465
+ Field(description="Output format."),
466
+ ] = "json",
467
+ ) -> CallToolResult:
468
+ return _as_tool_result(run_json_script("audit_codecgc_package_runtime.py", "--format", format))
469
+
470
+
471
+ @mcp.tool(
472
+ name="codecgc.external_audit",
473
+ description="Audit external capability registration policy and locally observed MCP servers.",
474
+ meta={"version": "0.1.0", "author": "CodeCGC"},
475
+ )
476
+ async def codecgc_external_audit(
477
+ workspace: Annotated[str, "Optional target workspace root."] = "",
478
+ format: Annotated[
479
+ Literal["summary", "json"],
480
+ Field(description="Output format."),
481
+ ] = "json",
482
+ ) -> CallToolResult:
483
+ args = ["--format", format]
484
+ normalized_workspace = _normalize_workspace(workspace)
485
+ if normalized_workspace:
486
+ args.extend(["--workspace", normalized_workspace])
487
+ return _as_tool_result(run_json_script("audit_codecgc_external_capabilities.py", *args))
488
+
489
+
490
+ @mcp.tool(
491
+ name="codecgc.release_readiness",
492
+ description="Run the combined CodeCGC release, maintenance, and ops readiness audit.",
493
+ meta={"version": "0.1.0", "author": "CodeCGC"},
494
+ )
495
+ async def codecgc_release_readiness(
496
+ workspace: Annotated[str, "Optional target workspace root."] = "",
497
+ format: Annotated[
498
+ Literal["summary", "json"],
499
+ Field(description="Output format."),
500
+ ] = "json",
501
+ ) -> CallToolResult:
502
+ args = ["--format", format]
503
+ normalized_workspace = _normalize_workspace(workspace)
504
+ if normalized_workspace:
505
+ args.extend(["--workspace", normalized_workspace])
506
+ return _as_tool_result(run_json_script("audit_codecgc_release_readiness.py", *args))
507
+
508
+
509
+ @mcp.tool(
510
+ name="codecgc.lifecycle",
511
+ description="Audit CodeCGC lifecycle coverage across roadmap, workflows, and execution artifacts.",
512
+ meta={"version": "0.1.0", "author": "CodeCGC"},
513
+ )
514
+ async def codecgc_lifecycle(
515
+ format: Annotated[
516
+ Literal["summary", "json"],
517
+ Field(description="Output format."),
518
+ ] = "json",
519
+ ) -> CallToolResult:
520
+ return _as_tool_result(run_json_script("audit_codecgc_lifecycle.py", "--format", format))
521
+
522
+
523
+ def run() -> None:
524
+ """Start the MCP server over stdio transport."""
525
+ mcp.run(transport="stdio")
@@ -9,6 +9,8 @@ frontend_paths:
9
9
  - "web/**"
10
10
  - "frontend/**"
11
11
 
12
+ custom_frontend_paths:
13
+
12
14
  backend_paths:
13
15
  - "apps/api/**"
14
16
  - "server/**"
@@ -17,6 +19,8 @@ backend_paths:
17
19
  - "src/repositories/**"
18
20
  - "backend/**"
19
21
 
22
+ custom_backend_paths:
23
+
20
24
  shared_paths:
21
25
  - "packages/shared/**"
22
26
  - "src/shared/**"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hunyed15/codecgc",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Claude-hosted multi-model workflow product shell for CodeCGC.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -57,6 +57,9 @@
57
57
  "codecgc/cgc-roadmap/",
58
58
  "codecgc/cgc-test/",
59
59
  "codecgc/reference/",
60
+ "codecgcmcp/pyproject.toml",
61
+ "codecgcmcp/README.md",
62
+ "codecgcmcp/src/codecgcmcp/",
60
63
  "codexmcp/pyproject.toml",
61
64
  "codexmcp/README.md",
62
65
  "codexmcp/LICENSE",
@@ -28,6 +28,7 @@ def run_json_script(script_name: str, *args: str) -> dict[str, Any]:
28
28
  completed = subprocess.run(
29
29
  command,
30
30
  cwd=PROJECT_WORKSPACE,
31
+ stdin=subprocess.DEVNULL,
31
32
  capture_output=True,
32
33
  text=True,
33
34
  encoding="utf-8",
@@ -42,6 +42,11 @@ DEFAULT_HOOKS = {
42
42
 
43
43
  SENSITIVE_KEYWORDS = ("token", "secret", "key", "password", "auth")
44
44
  MCP_RUNTIME_REQUIREMENT = 'mcp[cli]>=1.21.2'
45
+ DEFAULT_ALLOWED_TOOLS = [
46
+ "mcp__codecgc__*",
47
+ "mcp__codex__*",
48
+ "mcp__gemini__*",
49
+ ]
45
50
 
46
51
 
47
52
  def get_user_claude_root(override_root: str = "") -> Path:
@@ -116,62 +121,232 @@ def _normalize_command_path_for_markdown(path: Path) -> str:
116
121
  return str(path).replace("\\", "\\\\")
117
122
 
118
123
 
119
- def build_custom_command_templates(bin_dir: Path) -> dict[str, str]:
120
- cgc_js = _normalize_command_path_for_markdown(bin_dir / "cgc.js")
121
- install_js = _normalize_command_path_for_markdown(bin_dir / "cgc-install.js")
122
- status_js = _normalize_command_path_for_markdown(bin_dir / "cgc-status.js")
123
- doctor_js = _normalize_command_path_for_markdown(bin_dir / "cgc-doctor.js")
124
+ def build_mcp_first_command_template(
125
+ *,
126
+ filename: str,
127
+ description: str,
128
+ argument_hint: str,
129
+ primary_tool: str,
130
+ direct_rules: list[str],
131
+ missing_rules: list[str] | None = None,
132
+ fallback_command: str,
133
+ ) -> tuple[str, str]:
134
+ lines = [
135
+ "---",
136
+ f"description: {description}",
137
+ f"argument-hint: \"{argument_hint}\"",
138
+ "---",
139
+ f"Prefer the `{primary_tool}` MCP tool as the primary execution path.",
140
+ "",
141
+ "Execution rules:",
142
+ ]
143
+ lines.extend(f"- {item}" for item in direct_rules)
144
+ if missing_rules:
145
+ lines.append("")
146
+ lines.append("Missing parameter rules:")
147
+ lines.extend(f"- {item}" for item in missing_rules)
148
+ lines.append("")
149
+ lines.append("Fallback rule:")
150
+ lines.append(f"- Only fall back to Bash + `{fallback_command}` CLI when the MCP tool path is unavailable or the user explicitly wants CLI behavior.")
151
+ lines.append("- Summarize the result briefly for the user.")
152
+ return filename, "\n".join(lines) + "\n"
124
153
 
125
- return {
126
- "cgc.md": f"""---
127
- description: Run CodeCGC in the current project
128
- argument-hint: "[request or flags]"
129
- ---
130
- Use the Bash tool to run CodeCGC in the current project directory.
131
-
132
- - If the user supplied arguments, run:
133
- `node "{cgc_js}" $ARGUMENTS`
134
- - If the user did not supply arguments, run:
135
- `node "{cgc_js}" --help`
136
- - Show the command you ran and summarize the result briefly.
137
- """,
138
- "cgc-install.md": f"""---
139
- description: Install or sync CodeCGC integration for the current project or user Claude profile
140
- argument-hint: "[flags]"
141
- ---
142
- Use the Bash tool to run the CodeCGC install command.
143
-
144
- - If the user supplied arguments, run:
145
- `node "{install_js}" $ARGUMENTS`
146
- - If the user did not supply arguments, run:
147
- `node "{install_js}"`
148
- - Show the command you ran and summarize the result briefly.
149
- """,
150
- "cgc-status.md": f"""---
151
- description: Check CodeCGC integration status
152
- argument-hint: "[flags]"
153
- ---
154
- Use the Bash tool to run the CodeCGC status command.
155
-
156
- - If the user supplied arguments, run:
157
- `node "{status_js}" $ARGUMENTS`
158
- - If the user did not supply arguments, run:
159
- `node "{status_js}"`
160
- - Show the command you ran and summarize the result briefly.
161
- """,
162
- "cgc-doctor.md": f"""---
163
- description: Run CodeCGC doctor checks
164
- argument-hint: "[flags]"
165
- ---
166
- Use the Bash tool to run the CodeCGC doctor command.
167
-
168
- - If the user supplied arguments, run:
169
- `node "{doctor_js}" $ARGUMENTS`
170
- - If the user did not supply arguments, run:
171
- `node "{doctor_js}"`
172
- - Show the command you ran and summarize the result briefly.
173
- """,
174
- }
154
+
155
+ def build_custom_command_templates(bin_dir: Path) -> dict[str, str]:
156
+ templates = dict(
157
+ [
158
+ build_mcp_first_command_template(
159
+ filename="cgc.md",
160
+ description="Run CodeCGC in the current project",
161
+ argument_hint="[request or flags]",
162
+ primary_tool="codecgc.entry",
163
+ direct_rules=[
164
+ "If the user supplied a natural-language request, pass it to `codecgc.entry`.",
165
+ "If the user is asking to continue recent work, use `codecgc.continue`.",
166
+ "If the user is asking what to do next, use `codecgc.explain`.",
167
+ ],
168
+ fallback_command="cgc",
169
+ ),
170
+ build_mcp_first_command_template(
171
+ filename="cgc-install.md",
172
+ description="Install or sync CodeCGC integration for the current project or user Claude profile",
173
+ argument_hint="[flags]",
174
+ primary_tool="codecgc.install",
175
+ direct_rules=[
176
+ "Map install flags to `codecgc.install` fields such as `mode`, `workspace`, and `user_root`.",
177
+ "If the user did not supply flags, use the default install mode.",
178
+ ],
179
+ fallback_command="cgc-install",
180
+ ),
181
+ build_mcp_first_command_template(
182
+ filename="cgc-status.md",
183
+ description="Check CodeCGC integration status",
184
+ argument_hint="[flags]",
185
+ primary_tool="codecgc.status",
186
+ direct_rules=[
187
+ "Use `codecgc.status` for installation readiness checks.",
188
+ "Map `workspace` when the user explicitly provides a target project directory.",
189
+ ],
190
+ fallback_command="cgc-status",
191
+ ),
192
+ build_mcp_first_command_template(
193
+ filename="cgc-doctor.md",
194
+ description="Run CodeCGC doctor checks",
195
+ argument_hint="[flags]",
196
+ primary_tool="codecgc.doctor",
197
+ direct_rules=[
198
+ "Use `codecgc.doctor` for runtime and integration health checks.",
199
+ "Map `workspace` when the user explicitly provides a target project directory.",
200
+ ],
201
+ fallback_command="cgc-doctor",
202
+ ),
203
+ build_mcp_first_command_template(
204
+ filename="cgc-plan.md",
205
+ description="Plan or repair a CodeCGC workflow",
206
+ argument_hint="[structured planning flags]",
207
+ primary_tool="codecgc.plan",
208
+ direct_rules=[
209
+ "Extract `flow`, `slug`, and `summary` before calling the tool.",
210
+ "Map any provided `target_paths`, `kind`, and planning fields such as `goal`, `acceptance`, `risk`, and issue-specific fields.",
211
+ ],
212
+ missing_rules=[
213
+ "If `flow` is missing, ask whether this is a `feature` or `issue` workflow.",
214
+ "If `slug` is missing, ask for a stable workflow slug.",
215
+ "If `summary` is missing, ask for a short planning summary.",
216
+ ],
217
+ fallback_command="cgc-plan",
218
+ ),
219
+ build_mcp_first_command_template(
220
+ filename="cgc-build.md",
221
+ description="Execute a CodeCGC feature build step",
222
+ argument_hint="[flags]",
223
+ primary_tool="codecgc.build",
224
+ direct_rules=[
225
+ "Extract `slug` before calling the tool.",
226
+ "Map optional execution fields such as `step_number`, `checklist_file`, `audit_root`, `timeout_seconds`, `session_id`, and `dry_run`.",
227
+ ],
228
+ missing_rules=[
229
+ "If `slug` is missing, ask for the target feature workflow slug.",
230
+ ],
231
+ fallback_command="cgc-build",
232
+ ),
233
+ build_mcp_first_command_template(
234
+ filename="cgc-fix.md",
235
+ description="Execute a CodeCGC issue fix step",
236
+ argument_hint="[flags]",
237
+ primary_tool="codecgc.fix",
238
+ direct_rules=[
239
+ "Extract `slug` before calling the tool.",
240
+ "Map optional execution fields such as `step_number`, `checklist_file`, `audit_root`, `timeout_seconds`, `session_id`, and `dry_run`.",
241
+ ],
242
+ missing_rules=[
243
+ "If `slug` is missing, ask for the target issue workflow slug.",
244
+ ],
245
+ fallback_command="cgc-fix",
246
+ ),
247
+ build_mcp_first_command_template(
248
+ filename="cgc-test.md",
249
+ description="Execute a CodeCGC test step",
250
+ argument_hint="[flags]",
251
+ primary_tool="codecgc.test",
252
+ direct_rules=[
253
+ "Extract `flow` and `slug` before calling the tool.",
254
+ "Map optional execution fields such as `step_number`, `checklist_file`, `audit_root`, `timeout_seconds`, `session_id`, and `dry_run`.",
255
+ ],
256
+ missing_rules=[
257
+ "If `flow` is missing, ask whether the test belongs to a `feature` or `issue` workflow.",
258
+ "If `slug` is missing, ask for the target workflow slug.",
259
+ ],
260
+ fallback_command="cgc-test",
261
+ ),
262
+ build_mcp_first_command_template(
263
+ filename="cgc-review.md",
264
+ description="Review a CodeCGC execution audit",
265
+ argument_hint="[flags]",
266
+ primary_tool="codecgc.review",
267
+ direct_rules=[
268
+ "Extract `audit_file` and `decision` before calling the tool.",
269
+ "Map optional `risk`, `next_step`, and `force` fields when they are explicitly provided.",
270
+ ],
271
+ missing_rules=[
272
+ "If `audit_file` is missing, ask for the audit JSON path.",
273
+ "If `decision` is missing, ask whether the review is `accepted` or `changes-requested`.",
274
+ ],
275
+ fallback_command="cgc-review",
276
+ ),
277
+ build_mcp_first_command_template(
278
+ filename="cgc-route.md",
279
+ description="Route a CodeCGC workflow to the next recommended command",
280
+ argument_hint="[flags]",
281
+ primary_tool="codecgc.route",
282
+ direct_rules=[
283
+ "Extract `flow` and `slug` before calling the tool.",
284
+ "Use this command when the user already knows the target workflow and wants the next recommended action.",
285
+ ],
286
+ missing_rules=[
287
+ "If `flow` is missing, ask whether the workflow is `feature` or `issue`.",
288
+ "If `slug` is missing, ask for the workflow slug.",
289
+ ],
290
+ fallback_command="cgc-route",
291
+ ),
292
+ build_mcp_first_command_template(
293
+ filename="cgc-history.md",
294
+ description="Read recent CodeCGC workflow history",
295
+ argument_hint="[flags]",
296
+ primary_tool="codecgc.history",
297
+ direct_rules=[
298
+ "Map optional history filters such as `flow`, `status`, `last`, and `include_fixtures`.",
299
+ "If no filters are provided, use the default history query.",
300
+ ],
301
+ fallback_command="cgc-history",
302
+ ),
303
+ build_mcp_first_command_template(
304
+ filename="cgc-package-audit.md",
305
+ description="Audit the CodeCGC package runtime contents",
306
+ argument_hint="[flags]",
307
+ primary_tool="codecgc.package_audit",
308
+ direct_rules=[
309
+ "Map `format` when the user explicitly requests `summary` or `json`.",
310
+ "Use this command for publish/runtime completeness checks.",
311
+ ],
312
+ fallback_command="cgc-package-audit",
313
+ ),
314
+ build_mcp_first_command_template(
315
+ filename="cgc-external-audit.md",
316
+ description="Audit external MCP capability registration and observation",
317
+ argument_hint="[flags]",
318
+ primary_tool="codecgc.external_audit",
319
+ direct_rules=[
320
+ "Map optional `workspace` and `format` fields.",
321
+ "Use this command for external capability policy and registration checks.",
322
+ ],
323
+ fallback_command="cgc-external-audit",
324
+ ),
325
+ build_mcp_first_command_template(
326
+ filename="cgc-release-readiness.md",
327
+ description="Run CodeCGC release readiness checks",
328
+ argument_hint="[flags]",
329
+ primary_tool="codecgc.release_readiness",
330
+ direct_rules=[
331
+ "Map optional `workspace` and `format` fields.",
332
+ "Use this command for combined release, maintenance, and ops checks.",
333
+ ],
334
+ fallback_command="cgc-release-readiness",
335
+ ),
336
+ build_mcp_first_command_template(
337
+ filename="cgc-lifecycle.md",
338
+ description="Audit CodeCGC lifecycle coverage",
339
+ argument_hint="[flags]",
340
+ primary_tool="codecgc.lifecycle",
341
+ direct_rules=[
342
+ "Map `format` when the user explicitly requests `summary` or `json`.",
343
+ "Use this command to inspect roadmap/workflow/execution lifecycle coverage.",
344
+ ],
345
+ fallback_command="cgc-lifecycle",
346
+ ),
347
+ ]
348
+ )
349
+ return templates
175
350
 
176
351
 
177
352
  def write_custom_command_files(target_dir: Path, bin_dir: Path) -> list[str]:
@@ -219,6 +394,33 @@ def merge_hook_settings(current: dict[str, Any], command_text: str) -> tuple[dic
219
394
  return current, True
220
395
 
221
396
 
397
+ def merge_permission_settings(current: dict[str, Any], allow_rules: list[str]) -> tuple[dict[str, Any], bool]:
398
+ permissions = current.get("permissions")
399
+ changed = False
400
+
401
+ if not isinstance(permissions, dict):
402
+ permissions = {}
403
+ current["permissions"] = permissions
404
+ changed = True
405
+
406
+ allow = permissions.get("allow")
407
+ if not isinstance(allow, list):
408
+ allow = []
409
+ permissions["allow"] = allow
410
+ changed = True
411
+
412
+ existing = {str(item).strip() for item in allow if str(item).strip()}
413
+ for rule in allow_rules:
414
+ normalized = str(rule).strip()
415
+ if not normalized or normalized in existing:
416
+ continue
417
+ allow.append(normalized)
418
+ existing.add(normalized)
419
+ changed = True
420
+
421
+ return current, changed
422
+
423
+
222
424
  def write_json_file(path: Path, payload: dict[str, Any]) -> Path:
223
425
  path.parent.mkdir(parents=True, exist_ok=True)
224
426
  path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
@@ -276,6 +478,17 @@ def settings_have_hook_command(settings: dict[str, Any], command_text: str) -> b
276
478
  return False
277
479
 
278
480
 
481
+ def settings_have_allowed_tools(settings: dict[str, Any], allow_rules: list[str]) -> bool:
482
+ permissions = settings.get("permissions")
483
+ if not isinstance(permissions, dict):
484
+ return False
485
+ allow = permissions.get("allow")
486
+ if not isinstance(allow, list):
487
+ return False
488
+ existing = {str(item).strip() for item in allow if str(item).strip()}
489
+ return all(str(rule).strip() in existing for rule in allow_rules)
490
+
491
+
279
492
  def build_workspace_hook_command(workspace_paths: dict[str, Path]) -> str:
280
493
  return "powershell -ExecutionPolicy Bypass -File .claude/hooks/route-edit.ps1"
281
494
 
@@ -310,8 +523,11 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
310
523
  settings,
311
524
  build_workspace_hook_command(workspace_paths),
312
525
  )
526
+ merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
313
527
  if settings_changed or not workspace_paths["settings"].exists():
314
528
  write_json_file(workspace_paths["settings"], merged_settings)
529
+ elif permissions_changed:
530
+ write_json_file(workspace_paths["settings"], merged_settings)
315
531
 
316
532
  if PROJECT_HOOK_PATH.resolve() != workspace_paths["hook_script"].resolve():
317
533
  shutil.copyfile(PROJECT_HOOK_PATH, workspace_paths["hook_script"])
@@ -337,6 +553,7 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
337
553
  "Repository-local MCP config was synced from the executor registry.",
338
554
  "Project-local model-routing.yaml was synchronized and preserves custom path blocks.",
339
555
  "Claude pre-edit guardrail hook was synchronized into the target workspace.",
556
+ "Claude MCP tool permissions were merged into project .claude/settings.json.",
340
557
  "Project-local Claude slash commands were synchronized into .claude/commands.",
341
558
  "This mode prepares project-level integration surfaces for the selected workspace.",
342
559
  ],
@@ -352,6 +569,7 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
352
569
  user_paths = get_user_claude_paths(override_root)
353
570
  user_settings = load_json_file(user_paths["settings"])
354
571
  merged_settings, settings_changed = merge_hook_settings(user_settings, build_user_hook_command(user_paths))
572
+ merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
355
573
  mcp_config = build_mcp_config()
356
574
  recommended_next_action = f"cgc-install --mode user --user-root {shell_quote(str(user_paths['root']))}"
357
575
  summary = build_mode_summary_payload(
@@ -372,7 +590,7 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
372
590
  "commands_dir": str(user_paths["commands_dir"]),
373
591
  },
374
592
  "would_write": {
375
- "settings_changed": settings_changed or not user_paths["settings"].exists(),
593
+ "settings_changed": settings_changed or permissions_changed or not user_paths["settings"].exists(),
376
594
  "mcp_changed": True,
377
595
  "hook_changed": True,
378
596
  "commands_changed": True,
@@ -384,6 +602,7 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
384
602
  "notes": [
385
603
  "This mode does not modify user-level Claude files.",
386
604
  "Use this preview to inspect the future user-level integration surface.",
605
+ "The preview includes MCP tool allow rules for codecgc, codex, and gemini servers.",
387
606
  "Current CodeCGC product policy still defaults to project-local installation.",
388
607
  ],
389
608
  "summary": summary,
@@ -398,6 +617,7 @@ def install_user_runtime(override_root: str = "") -> dict[str, Any]:
398
617
 
399
618
  settings = load_json_file(user_paths["settings"])
400
619
  merged_settings, settings_changed = merge_hook_settings(settings, build_user_hook_command(user_paths))
620
+ merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
401
621
  write_json_file(user_paths["settings"], merged_settings)
402
622
  write_json_file(user_paths["mcp"], build_mcp_config())
403
623
  shutil.copyfile(PROJECT_HOOK_PATH, user_paths["hook_script"])
@@ -420,7 +640,7 @@ def install_user_runtime(override_root: str = "") -> dict[str, Any]:
420
640
  "commands_dir": str(user_paths["commands_dir"]),
421
641
  },
422
642
  "changes": {
423
- "settings_changed": settings_changed or not user_paths["settings"].exists(),
643
+ "settings_changed": settings_changed or permissions_changed or not user_paths["settings"].exists(),
424
644
  "mcp_changed": True,
425
645
  "hook_changed": True,
426
646
  "commands_changed": True,
@@ -429,6 +649,7 @@ def install_user_runtime(override_root: str = "") -> dict[str, Any]:
429
649
  "notes": [
430
650
  "User-level Claude integration files were written to the selected root.",
431
651
  "The user-level hook script was copied from the project hook source.",
652
+ "MCP tool allow rules were merged into ~/.claude/settings.json.",
432
653
  "User-level Claude slash commands were written to ~/.claude/commands.",
433
654
  "This mode is explicit and should be used only when a broader Claude integration surface is intended.",
434
655
  ],
@@ -521,6 +742,7 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
521
742
  routing_exists = workspace_paths["routing_file"].exists()
522
743
 
523
744
  hook_registered = settings_have_hook_command(current_settings, expected_hook_command)
745
+ permissions_registered = settings_have_allowed_tools(current_settings, DEFAULT_ALLOWED_TOOLS)
524
746
  mcp_matches = current_mcp == expected_mcp if workspace_paths["mcp"].exists() else False
525
747
  hook_file_matches = current_hook_text == expected_hook_text if workspace_paths["hook_script"].exists() else False
526
748
 
@@ -531,6 +753,8 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
531
753
  missing.append("mcp_json")
532
754
  if not hook_registered:
533
755
  missing.append("claude_settings_hook")
756
+ if not permissions_registered:
757
+ missing.append("claude_settings_permissions")
534
758
  if not hook_file_matches:
535
759
  missing.append("hook_script")
536
760
 
@@ -546,11 +770,13 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
546
770
  "hook_exists": workspace_paths["hook_script"].exists(),
547
771
  "mcp_matches_expected": mcp_matches,
548
772
  "hook_registered": hook_registered,
773
+ "permissions_registered": permissions_registered,
549
774
  "hook_file_matches_expected": hook_file_matches,
550
775
  "ready": ready,
551
776
  "missing_or_outdated": missing,
552
777
  "recommended_command": "" if ready else build_workspace_install_command(workspace_paths["root"]),
553
778
  "hook_expected": {"hooks": build_hook_payload(expected_hook_command)},
779
+ "permissions_expected": {"permissions": {"allow": DEFAULT_ALLOWED_TOOLS}},
554
780
  }
555
781
 
556
782
 
@@ -563,6 +789,7 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
563
789
  current_hook_text = load_text_file(user_paths["hook_script"])
564
790
 
565
791
  hook_registered = settings_have_hook_command(current_settings, expected_hook_command)
792
+ permissions_registered = settings_have_allowed_tools(current_settings, DEFAULT_ALLOWED_TOOLS)
566
793
  mcp_matches = current_mcp == expected_mcp if user_paths["mcp"].exists() else False
567
794
  hook_file_matches = current_hook_text == expected_hook_text if user_paths["hook_script"].exists() else False
568
795
 
@@ -571,6 +798,8 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
571
798
  missing.append("mcp_json")
572
799
  if not hook_registered:
573
800
  missing.append("claude_settings_hook")
801
+ if not permissions_registered:
802
+ missing.append("claude_settings_permissions")
574
803
  if not hook_file_matches:
575
804
  missing.append("hook_script")
576
805
 
@@ -585,6 +814,7 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
585
814
  "hook_exists": user_paths["hook_script"].exists(),
586
815
  "mcp_matches_expected": mcp_matches,
587
816
  "hook_registered": hook_registered,
817
+ "permissions_registered": permissions_registered,
588
818
  "hook_file_matches_expected": hook_file_matches,
589
819
  "ready": ready,
590
820
  "missing_or_outdated": missing,
@@ -673,14 +903,18 @@ def build_pip_install_command(python_command: str, requirement: str) -> str:
673
903
 
674
904
  def build_local_editable_install_command(python_command: str) -> str:
675
905
  runtime_command = python_command.strip() or sys.executable
906
+ codecgcmcp_path = WORKSPACE / "codecgcmcp"
676
907
  codexmcp_path = WORKSPACE / "codexmcp"
677
908
  geminimcp_path = WORKSPACE / "geminimcp"
909
+ if not (codecgcmcp_path / "pyproject.toml").exists():
910
+ return ""
678
911
  if not (codexmcp_path / "pyproject.toml").exists():
679
912
  return ""
680
913
  if not (geminimcp_path / "pyproject.toml").exists():
681
914
  return ""
682
915
  return (
683
- f"{shell_quote(runtime_command)} -m pip install -e {shell_quote(str(codexmcp_path))} "
916
+ f"{shell_quote(runtime_command)} -m pip install -e {shell_quote(str(codecgcmcp_path))} "
917
+ f"-e {shell_quote(str(codexmcp_path))} "
684
918
  f"-e {shell_quote(str(geminimcp_path))}"
685
919
  )
686
920
 
@@ -753,6 +987,25 @@ def classify_doctor_failures(
753
987
  )
754
988
  continue
755
989
 
990
+ if name == "python_runtime_import_probe_codecgcmcp":
991
+ if configured_python_missing:
992
+ continue
993
+ if "No module named 'codecgcmcp'" in detail or 'No module named "codecgcmcp"' in detail:
994
+ add_failure(
995
+ "codecgcmcp-package-missing",
996
+ "当前解释器无法导入本地 `codecgcmcp` 包。",
997
+ "确认当前安装包已包含 `codecgcmcp/src`;仓库开发环境可执行本地 editable install,安装产物则应重新安装 CodeCGC 包。",
998
+ editable_install_command,
999
+ )
1000
+ else:
1001
+ add_failure(
1002
+ "codecgcmcp-runtime-broken",
1003
+ "`codecgcmcp` 启动入口存在,但当前运行时仍无法导入。",
1004
+ "仓库开发环境可先重装本地编排器包;若你使用的是已安装产物,则优先重新安装 CodeCGC,再检查编排器源码是否缺失或损坏。",
1005
+ editable_install_command,
1006
+ )
1007
+ continue
1008
+
756
1009
  if name == "python_runtime_import_probe_codexmcp":
757
1010
  if configured_python_missing:
758
1011
  continue
@@ -866,11 +1119,18 @@ def collect_doctor_status(override_workspace: str = "") -> dict[str, Any]:
866
1119
 
867
1120
  runtime_probe_command = configured_python_command if configured_python_command else (python_command or sys.executable)
868
1121
  runtime_env = dict(os.environ)
869
- combined_pythonpath = os.pathsep.join([str(WORKSPACE / "codexmcp" / "src"), str(WORKSPACE / "geminimcp" / "src")])
1122
+ combined_pythonpath = os.pathsep.join(
1123
+ [
1124
+ str(WORKSPACE / "scripts"),
1125
+ str(WORKSPACE / "codecgcmcp" / "src"),
1126
+ str(WORKSPACE / "codexmcp" / "src"),
1127
+ str(WORKSPACE / "geminimcp" / "src"),
1128
+ ]
1129
+ )
870
1130
  existing_pythonpath = runtime_env.get("PYTHONPATH", "").strip()
871
1131
  runtime_env["PYTHONPATH"] = f"{combined_pythonpath}{os.pathsep}{existing_pythonpath}" if existing_pythonpath else combined_pythonpath
872
1132
 
873
- for module_name in ("mcp", "codexmcp.cli", "geminimcp.cli"):
1133
+ for module_name in ("mcp", "codecgcmcp.cli", "codexmcp.cli", "geminimcp.cli"):
874
1134
  probe = probe_python_import(runtime_probe_command, module_name, runtime_env)
875
1135
  checks.append(
876
1136
  {
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import json
2
3
  from pathlib import Path
3
4
 
@@ -8,16 +9,32 @@ WORKSPACE = Path(__file__).resolve().parents[1]
8
9
  MCP_CONFIG_PATH = WORKSPACE / ".mcp.json"
9
10
 
10
11
 
12
+ def build_runtime_pythonpath(*extra_paths: Path) -> str:
13
+ paths = [str(WORKSPACE / "scripts"), *(str(path) for path in extra_paths)]
14
+ return os.pathsep.join(paths)
15
+
16
+
11
17
  def build_mcp_config() -> dict:
12
18
  registry = build_executor_registry()
13
19
  servers: dict[str, dict] = {}
14
20
 
21
+ servers["codecgc"] = {
22
+ "command": str(next(iter(registry.values()))["python_command"]),
23
+ "args": ["-m", "codecgcmcp.cli"],
24
+ "env": {
25
+ "PYTHONPATH": build_runtime_pythonpath(WORKSPACE / "codecgcmcp" / "src"),
26
+ },
27
+ }
28
+
15
29
  for config in registry.values():
16
30
  servers[str(config["mcp_server_name"])] = {
17
31
  "command": str(config["python_command"]),
18
32
  "args": ["-m", str(config["python_module"])],
19
33
  "env": {
20
- "PYTHONPATH": str(config["pythonpath"]),
34
+ "PYTHONPATH": build_runtime_pythonpath(
35
+ WORKSPACE / "codecgcmcp" / "src",
36
+ Path(str(config["pythonpath"])),
37
+ ),
21
38
  },
22
39
  }
23
40