@@ -350,6 +350,16 @@ def init(config_path: str | None, output_dir: str, no_git: bool, guided: bool) -
350350
351351 write_phase (target , "inception" )
352352
353+ # Auto-register with the MCP governance server (best-effort, never blocks)
354+ with contextlib .suppress (Exception ):
355+ from specsmith .mcp_server import register_project
356+
357+ if register_project (str (target )):
358+ console .print (
359+ " [dim]\u2713 Registered with MCP server "
360+ "([bold]specsmith mcp projects[/bold] to view)[/dim]"
361+ )
362+
353363
354364def _load_config_with_inheritance (config_path : str ) -> dict [str , object ]:
355365 """Load scaffold.yml, merging parent config if `extends` is set."""
@@ -1746,6 +1756,16 @@ def import_project(
17461756 console .print (f"\n [bold green]Done.[/bold green] { len (created )} governance files generated." )
17471757 console .print ("Governance files generated. Review project configuration." )
17481758
1759+ # Auto-register with the MCP governance server (best-effort, never blocks)
1760+ with contextlib .suppress (Exception ):
1761+ from specsmith .mcp_server import register_project
1762+
1763+ if register_project (str (root )):
1764+ console .print (
1765+ " [dim]\u2713 Registered with MCP server "
1766+ "([bold]specsmith mcp projects[/bold] to view)[/dim]"
1767+ )
1768+
17491769
17501770def _run_guided_architecture (cfg : ProjectConfig , target : Path ) -> list [Path ]:
17511771 """Run interactive architecture definition and generate REQ/TEST stubs."""
@@ -9049,46 +9069,45 @@ def mcp_list_cmd(project_dir: str, as_json: bool) -> None:
90499069
90509070
90519071@mcp_group .command (name = "serve" )
9052- @click .option ("--project-dir" , type = click .Path (), default = "." , show_default = True )
9072+ @click .option (
9073+ "--project-dir" ,
9074+ type = click .Path (),
9075+ default = None ,
9076+ help = (
9077+ "Set this path as the *primary* project (first slot / default for tool calls "
9078+ "that omit project_dir). Omit to use the registry automatically."
9079+ ),
9080+ )
90539081@click .option (
90549082 "--project-dirs" ,
90559083 default = "" ,
90569084 help = (
9057- "Additional project directories to register (comma-separated absolute paths). "
9058- "All registered projects are accessible to any tool call via their "
9059- "absolute path as the project_dir argument."
9085+ "Extra project directories to add on top of the registry "
9086+ "(comma-separated absolute paths)."
90609087 ),
90619088)
9062- def mcp_serve_cmd (project_dir : str , project_dirs : str ) -> None :
9089+ def mcp_serve_cmd (project_dir : str | None , project_dirs : str ) -> None :
90639090 """Start the native governance MCP stdio server (REQ-363).
90649091
90659092 Implements MCP 2024-11-05 over stdin/stdout (JSON-RPC 2.0).
9066- Exposes seven governance tools to any MCP client:
9093+ Exposes seven governance tools to any MCP client.
90679094
90689095 \b
9069- governance_project_list List all registered projects
9070- governance_audit Run governance audit
9071- governance_checkpoint Emit GOVERNANCE ANCHOR JSON
9072- governance_preflight Preflight a change intent
9073- governance_phase Current AEE phase + readiness %
9074- governance_req_list List all requirements
9075- governance_trace_seal Seal a milestone/decision
9096+ Recommended Warp config (set once, never touch again)::
9097+
9098+ {"specsmith-governance": {"command": "specsmith", "args": ["mcp", "serve"]}}
90769099
90779100 \b
9078- Single-project Warp config (Settings → Agents → MCP servers) ::
9101+ Then register each project once from inside that project ::
90799102
9080- {"specsmith-governance": {"command": "specsmith",
9081- "args": ["mcp", "serve", "--project-dir", "/path/to/project"]}}
9103+ specsmith mcp register
90829104
90839105 \b
9084- Multi-project Warp config::
9085-
9086- {"specsmith-governance": {"command": "specsmith",
9087- "args": ["mcp", "serve", "--project-dir", "/path/to/proj1",
9088- "--project-dirs", "/path/to/proj2,/path/to/proj3"]}}
9106+ The server reads the registry at startup and serves all registered
9107+ projects automatically — no config changes needed for new projects.
90899108
90909109 \b
9091- Or pass inline to oz (use ``specsmith mcp install-warp`` for the full snippet)::
9110+ Or pass inline to oz (use ``specsmith mcp install-warp`` for the snippet)::
90929111
90939112 oz agent run --mcp "$(specsmith mcp install-warp --json)" --prompt "..."
90949113 """
@@ -9099,21 +9118,17 @@ def mcp_serve_cmd(project_dir: str, project_dirs: str) -> None:
90999118
91009119
91019120@mcp_group .command (name = "install-warp" )
9102- @click .option ("--project-dir" , type = click .Path (), default = "." , show_default = True )
9103- @click .option (
9104- "--project-dirs" ,
9105- default = "" ,
9106- help = "Additional project dirs (comma-separated) to include in the multi-project config." ,
9107- )
91089121@click .option ("--json" , "as_json" , is_flag = True , default = False , help = "Emit JSON config only." )
9109- def mcp_install_warp_cmd (project_dir : str , project_dirs : str , as_json : bool ) -> None :
9122+ def mcp_install_warp_cmd (as_json : bool ) -> None :
91109123 """Print the Warp MCP config snippet for the governance server (REQ-363).
91119124
9125+ Generates a minimal, registry-aware config — paste it into Warp once
9126+ and never change it again. Register new projects with::
9127+
9128+ specsmith mcp register # in the project directory
9129+
91129130 Copy the output into Warp Settings → Agents → MCP servers, or pass it
91139131 to ``oz agent run --mcp '<json>'`` for a one-off cloud agent run.
9114-
9115- For multi-project setups pass ``--project-dirs`` with comma-separated
9116- absolute paths; one server instance will serve all projects.
91179132 """
91189133 import json as _json
91199134 import shutil
@@ -9124,17 +9139,10 @@ def mcp_install_warp_cmd(project_dir: str, project_dirs: str, as_json: bool) ->
91249139 specsmith_exe = shutil .which ("specsmith" ) or str (
91259140 Path (sys .executable ).parent / ("specsmith.exe" if sys .platform == "win32" else "specsmith" )
91269141 )
9127- default_dir = str (Path (project_dir ).resolve ())
9128- args : list [str ] = ["mcp" , "serve" , "--project-dir" , default_dir ]
9129-
9130- extra_dirs = [p .strip () for p in project_dirs .split ("," ) if p .strip ()] if project_dirs else []
9131- if extra_dirs :
9132- args += ["--project-dirs" , "," .join (extra_dirs )]
9133-
91349142 config = {
91359143 "specsmith-governance" : {
91369144 "command" : specsmith_exe ,
9137- "args" : args ,
9145+ "args" : [ "mcp" , "serve" ] ,
91389146 }
91399147 }
91409148
@@ -9148,22 +9156,125 @@ def mcp_install_warp_cmd(project_dir: str, project_dirs: str, as_json: bool) ->
91489156 "or pass inline to [bold]oz agent run --mcp '<json>'[/bold]:\n "
91499157 )
91509158 console .print (_json .dumps (config , indent = 2 ))
9151- if extra_dirs :
9152- console .print (
9153- f"\n [dim]Serving { 1 + len (extra_dirs )} projects. "
9154- "Call [bold]governance_project_list[/bold] to see all registered paths,\n "
9155- "then pass any path as [bold]project_dir[/bold] to target a specific project.[/dim]"
9156- )
9159+ console .print (
9160+ "\n [dim][bold]One-time setup[/bold] — paste this config into Warp once," # noqa: E501
9161+ " then never touch it again.\n "
9162+ "\n To add each project, run this inside the project directory:\n "
9163+ " [bold]specsmith mcp register[/bold]\n "
9164+ "\n The server reads [bold]~/.specsmith/mcp-projects.json[/bold] at startup\n "
9165+ "and serves all registered projects automatically.\n "
9166+ "\n View registered projects: [bold]specsmith mcp projects[/bold]\n "
9167+ "\n Verify server: specsmith mcp serve (then send an initialize message).[/dim]"
9168+ )
9169+
9170+
9171+ @mcp_group .command (name = "register" )
9172+ @click .argument ("path" , default = "." , required = False )
9173+ def mcp_register_cmd (path : str ) -> None :
9174+ """Register a project directory with the MCP server registry.
9175+
9176+ Run once inside a project directory to add it to
9177+ ``~/.specsmith/mcp-projects.json``. The next ``specsmith mcp serve``
9178+ invocation will automatically include it — no Warp config changes needed.
9179+
9180+ \b
9181+ Examples::
9182+
9183+ specsmith mcp register # register current directory
9184+ specsmith mcp register /path/to/myproject
9185+ """
9186+ from specsmith .mcp_server import register_project
9187+
9188+ root = Path (path ).resolve ()
9189+ if not root .exists ():
9190+ console .print (f"[red]\u2717 [/red] Path does not exist: { root } " )
9191+ raise SystemExit (1 )
9192+
9193+ added = register_project (str (root ))
9194+ if added :
9195+ console .print (f"[green]\u2713 [/green] Registered: [bold]{ root } [/bold]" )
9196+ if not (root / ".specsmith" ).exists ():
9197+ console .print (
9198+ " [yellow]\u26a0 [/yellow] No .specsmith/ found. "
9199+ "Run [bold]specsmith init[/bold] or [bold]specsmith import[/bold] first."
9200+ )
91579201 else :
9202+ console .print (f"[dim]Already registered: { root } [/dim]" )
9203+ console .print (
9204+ " [dim]specsmith mcp projects ← view all registered[/dim]\n "
9205+ " [dim]specsmith mcp serve ← start the server[/dim]"
9206+ )
9207+
9208+
9209+ @mcp_group .command (name = "unregister" )
9210+ @click .argument ("path" , default = "." , required = False )
9211+ def mcp_unregister_cmd (path : str ) -> None :
9212+ """Remove a project directory from the MCP server registry.
9213+
9214+ \b
9215+ Examples::
9216+
9217+ specsmith mcp unregister # unregister current directory
9218+ specsmith mcp unregister /path/to/myproject
9219+ """
9220+ from specsmith .mcp_server import unregister_project
9221+
9222+ root = Path (path ).resolve ()
9223+ removed = unregister_project (str (root ))
9224+ if removed :
9225+ console .print (f"[green]\u2713 [/green] Unregistered: [bold]{ root } [/bold]" )
9226+ else :
9227+ console .print (f"[yellow]Not registered: { root } [/yellow]" )
9228+
9229+
9230+ @mcp_group .command (name = "projects" )
9231+ @click .option ("--json" , "as_json" , is_flag = True , default = False , help = "Emit as JSON." )
9232+ def mcp_projects_cmd (as_json : bool ) -> None :
9233+ """List all projects registered with the MCP server.
9234+
9235+ Shows each registered project path and whether it still exists on disk.
9236+ The first entry is the default (used when a tool call omits project_dir).
9237+ """
9238+ import json as _json
9239+ import os
9240+
9241+ from specsmith .mcp_server import _registry_file , read_registry
9242+
9243+ projects = read_registry ()
9244+ reg_path = _registry_file ()
9245+
9246+ if as_json :
9247+ entries = [
9248+ {"path" : p , "exists" : Path (p ).exists (), "is_default" : i == 0 }
9249+ for i , p in enumerate (projects )
9250+ ]
9251+ click .echo (_json .dumps ({"registry" : str (reg_path ), "projects" : entries }, indent = 2 ))
9252+ return
9253+
9254+ if not projects :
9255+ console .print ("[yellow]No projects registered.[/yellow]" )
91589256 console .print (
9159- "\n [dim]After adding, Warp/Oz can call governance_project_list, "
9160- "governance_audit, governance_preflight,\n "
9161- "governance_checkpoint, governance_phase, governance_req_list, and\n "
9162- "governance_trace_seal as structured MCP tool calls.\n "
9163- "\n For multiple projects add --project-dirs /path/a,/path/b to serve them "
9164- "all from one server instance.\n "
9165- "\n Verify with: specsmith mcp serve (then send an initialize message).[/dim]"
9257+ "[dim]Run [bold]specsmith mcp register[/bold] inside a project to add it.[/dim]"
91669258 )
9259+ return
9260+
9261+ console .print (
9262+ f"[bold]Registered MCP projects[/bold] ({ len (projects )} ) "
9263+ f"[dim]{ reg_path } [/dim]\n "
9264+ )
9265+ for i , p in enumerate (projects ):
9266+ exists = Path (p ).exists ()
9267+ default_tag = " [bold cyan][default][/bold cyan]" if i == 0 else ""
9268+ health = "[green]\u2713 exists[/green]" if exists else "[red]\u2717 not found[/red]"
9269+ # Abbreviate long paths using ~ for home
9270+ display = p .replace (os .path .expanduser ("~" ), "~" )
9271+ console .print (f" { health } { display } { default_tag } " )
9272+
9273+ console .print (
9274+ "\n [dim] specsmith mcp register [path] ← add a project"
9275+ "\n specsmith mcp unregister [path] ← remove a project"
9276+ "\n specsmith mcp serve ← start the server[/dim]"
9277+ )
91679278
91689279
91699280main .add_command (mcp_group )
0 commit comments