11"""
2- Project scaffolding: mcp-agent init (scaffold minimal version).
2+ Project scaffolding: mcp-agent init (scaffold minimal version or copy curated examples ).
33"""
44
55from __future__ import annotations
66
7+ import shutil
78from pathlib import Path
89from importlib import resources
10+ from importlib .resources import files as _pkg_files
911
1012import typer
1113from rich .console import Console
1416
1517app = typer .Typer (help = "Scaffold a new mcp-agent project" )
1618console = Console ()
19+ err_console = Console (stderr = True )
20+
21+ # Path to repository examples
22+ EXAMPLE_ROOT = Path (__file__ ).parents [4 ] / "examples"
1723
1824
1925def _load_template (template_name : str ) -> str :
@@ -74,11 +80,70 @@ def _write_readme(dir_path: Path, content: str, force: bool) -> str | None:
7480 return None
7581
7682
83+ def _copy_tree (src : Path , dst : Path , force : bool ) -> int :
84+ """Copy a directory tree from src to dst.
85+
86+ Returns 1 on success, 0 on failure.
87+ """
88+ if not src .exists ():
89+ err_console .print (f"[red]Source not found: { src } [/red]" )
90+ return 0
91+ try :
92+ if dst .exists ():
93+ if force :
94+ shutil .rmtree (dst )
95+ else :
96+ return 0
97+ shutil .copytree (src , dst )
98+ return 1
99+ except Exception as e :
100+ err_console .print (f"[red]Error copying tree: { e } [/red]" )
101+ return 0
102+
103+
104+ def _copy_pkg_tree (pkg_rel : str , dst : Path , force : bool ) -> int :
105+ """Copy packaged examples from mcp_agent.data/examples/<pkg_rel> into dst.
106+
107+ Uses importlib.resources to locate files installed with the package.
108+ Returns 1 on success, 0 on failure.
109+ """
110+ try :
111+ root = (
112+ _pkg_files ("mcp_agent" )
113+ .joinpath ("data" )
114+ .joinpath ("examples" )
115+ .joinpath (pkg_rel )
116+ )
117+ except Exception :
118+ return 0
119+ if not root .exists ():
120+ return 0
121+
122+ # Mirror directory tree
123+ def _copy_any (node , target : Path ):
124+ if node .is_dir ():
125+ target .mkdir (parents = True , exist_ok = True )
126+ for child in node .iterdir ():
127+ _copy_any (child , target / child .name )
128+ else :
129+ if target .exists () and not force :
130+ return
131+ with node .open ("rb" ) as rf :
132+ data = rf .read ()
133+ target .parent .mkdir (parents = True , exist_ok = True )
134+ with open (target , "wb" ) as wf :
135+ wf .write (data )
136+
137+ _copy_any (root , dst )
138+ return 1
139+
140+
77141@app .callback (invoke_without_command = True )
78142def init (
79143 ctx : typer .Context ,
80144 dir : Path = typer .Option (Path ("." ), "--dir" , "-d" , help = "Target directory" ),
81145 template : str = typer .Option ("basic" , "--template" , "-t" , help = "Template to use" ),
146+ quickstart : str = typer .Option (None , "--quickstart" , help = "Quickstart mode: copy example without config files" ),
82147 force : bool = typer .Option (False , "--force" , "-f" , help = "Overwrite existing files" ),
83148 no_gitignore : bool = typer .Option (
84149 False , "--no-gitignore" , help = "Skip creating .gitignore"
@@ -87,34 +152,122 @@ def init(
87152 False , "--list" , "-l" , help = "List available templates"
88153 ),
89154) -> None :
90- """Initialize a new MCP-Agent project with configuration and example files."""
155+ """Initialize a new MCP-Agent project with configuration and example files.
156+
157+ Use --template for full project initialization with config files.
158+ Use --quickstart for copying examples only."""
91159
92160 # Available templates with descriptions
93- templates = {
161+ # Organized into scaffolding templates and full example templates
162+ scaffolding_templates = {
94163 "basic" : "Simple agent with filesystem and fetch capabilities" ,
95164 "server" : "MCP server with workflow and parallel agents" ,
96165 "token" : "Token counting example with monitoring" ,
97166 "factory" : "Agent factory with router-based selection" ,
98167 "minimal" : "Minimal configuration files only" ,
99168 }
100169
170+ example_templates = {
171+ "workflow" : "Workflow examples (from examples/workflows)" ,
172+ "researcher" : "MCP researcher use case (from examples/usecases/mcp_researcher)" ,
173+ "data-analysis" : "Financial data analysis example" ,
174+ "state-transfer" : "Workflow router with state transfer" ,
175+ "mcp-basic-agent" : "Basic MCP agent example" ,
176+ "token-counter" : "Token counting with monitoring" ,
177+ "agent-factory" : "Agent factory pattern" ,
178+ "basic-agent-server" : "Basic agent server (asyncio)" ,
179+ "reference-agent-server" : "Reference agent server implementation" ,
180+ "elicitation" : "Elicitation server example" ,
181+ "sampling" : "Sampling server example" ,
182+ "notifications" : "Notifications server example" ,
183+ }
184+
185+ templates = {** scaffolding_templates , ** example_templates }
186+
101187 if list_templates :
102188 console .print ("\n [bold]Available Templates:[/bold]\n " )
103- table = Table (show_header = True , header_style = "cyan" )
104- table .add_column ("Template" , style = "green" )
105- table .add_column ("Description" )
106189
107- for name , desc in templates .items ():
108- table .add_row (name , desc )
190+ # Templates table
191+ console .print ("[bold cyan]Templates:[/bold cyan]" )
192+ console .print ("[dim]Creates minimal project structure with config files[/dim]\n " )
193+ table1 = Table (show_header = True , header_style = "cyan" )
194+ table1 .add_column ("Template" , style = "green" )
195+ table1 .add_column ("Description" )
196+ for name , desc in scaffolding_templates .items ():
197+ table1 .add_row (name , desc )
198+ console .print (table1 )
199+
200+ # Quickstart templates table
201+ console .print ("\n [bold cyan]Quickstart Templates:[/bold cyan]" )
202+ console .print ("[dim]Copies complete example projects[/dim]\n " )
203+ table2 = Table (show_header = True , header_style = "cyan" )
204+ table2 .add_column ("Template" , style = "green" )
205+ table2 .add_column ("Description" )
206+ for name , desc in example_templates .items ():
207+ table2 .add_row (name , desc )
208+ console .print (table2 )
109209
110- console .print (table )
111210 console .print ("\n [dim]Use: mcp-agent init --template <name>[/dim]" )
112211 return
113212
114213 if ctx .invoked_subcommand :
115214 return
116215
117- # Validate template
216+ if quickstart :
217+ if quickstart not in example_templates :
218+ console .print (f"[red]Unknown quickstart example: { quickstart } [/red]" )
219+ console .print (f"Available examples: { ', ' .join (example_templates .keys ())} " )
220+ console .print ("[dim]Use --list to see all available templates[/dim]" )
221+ raise typer .Exit (1 )
222+
223+ example_map = {
224+ "workflow" : (EXAMPLE_ROOT / "workflows" , "workflow" ),
225+ "researcher" : (EXAMPLE_ROOT / "usecases" / "mcp_researcher" , "researcher" ),
226+ "data-analysis" : (EXAMPLE_ROOT / "usecases" / "mcp_financial_analyzer" , "data-analysis" ),
227+ "state-transfer" : (EXAMPLE_ROOT / "workflows" / "workflow_router" , "state-transfer" ),
228+ "basic-agent-server" : (EXAMPLE_ROOT / "mcp_agent_server" / "asyncio" , "basic_agent_server" ),
229+ "mcp-basic-agent" : (None , "mcp_basic_agent" , "basic/mcp_basic_agent" ),
230+ "token-counter" : (None , "token_counter" , "basic/token_counter" ),
231+ "agent-factory" : (None , "agent_factory" , "basic/agent_factory" ),
232+ "reference-agent-server" : (None , "reference_agent_server" , "mcp_agent_server/reference" ),
233+ "elicitation" : (None , "elicitation" , "mcp_agent_server/elicitation" ),
234+ "sampling" : (None , "sampling" , "mcp_agent_server/sampling" ),
235+ "notifications" : (None , "notifications" , "mcp_agent_server/notifications" ),
236+ "hello-world" : (EXAMPLE_ROOT / "cloud" / "hello_world" , "hello_world" ),
237+ "mcp" : (EXAMPLE_ROOT / "cloud" / "mcp" , "mcp" ),
238+ "temporal" : (EXAMPLE_ROOT / "cloud" / "temporal" , "temporal" ),
239+ "chatgpt-app" : (EXAMPLE_ROOT / "cloud" / "chatgpt_app" , "chatgpt_app" ),
240+ }
241+
242+ mapping = example_map .get (quickstart )
243+ if not mapping :
244+ console .print (f"[red]Quickstart example '{ quickstart } ' not found[/red]" )
245+ raise typer .Exit (1 )
246+
247+ base_dir = dir .resolve ()
248+ base_dir .mkdir (parents = True , exist_ok = True )
249+
250+ if len (mapping ) == 3 :
251+ _ , dst_name , pkg_rel = mapping
252+ dst = base_dir / dst_name
253+ copied = _copy_pkg_tree (pkg_rel , dst , force )
254+ if not copied :
255+ src = EXAMPLE_ROOT / pkg_rel .replace ("/" , "_" )
256+ if src .exists ():
257+ copied = _copy_tree (src , dst , force )
258+ else :
259+ src , dst_name = mapping
260+ dst = base_dir / dst_name
261+ copied = _copy_tree (src , dst , force )
262+
263+ if copied :
264+ console .print (f"Copied { copied } set(s) to { dst } " )
265+ else :
266+ console .print (f"[yellow]Could not copy '{ quickstart } ' - destination may already exist[/yellow]" )
267+ console .print ("Use --force to overwrite" )
268+
269+ return
270+
118271 if template not in templates :
119272 console .print (f"[red]Unknown template: { template } [/red]" )
120273 console .print (f"Available templates: { ', ' .join (templates .keys ())} " )
@@ -150,7 +303,61 @@ def init(
150303 if gitignore_content and _write (gitignore_path , gitignore_content , force ):
151304 files_created .append (".gitignore" )
152305
153- # Create template-specific files
306+ # Handle example templates (copy from repository or package)
307+ if template in example_templates :
308+ # Map template names to their source paths
309+ # Format: "name": (repo_path, dest_name) for repo examples
310+ # "name": (None, dest_name, pkg_rel) for packaged examples
311+ example_map = {
312+ "workflow" : (EXAMPLE_ROOT / "workflows" , "workflow" ),
313+ "researcher" : (EXAMPLE_ROOT / "usecases" / "mcp_researcher" , "researcher" ),
314+ "data-analysis" : (EXAMPLE_ROOT / "usecases" / "mcp_financial_analyzer" , "data-analysis" ),
315+ "state-transfer" : (EXAMPLE_ROOT / "workflows" / "workflow_router" , "state-transfer" ),
316+ "basic-agent-server" : (EXAMPLE_ROOT / "mcp_agent_server" / "asyncio" , "basic_agent_server" ),
317+ "mcp-basic-agent" : (None , "mcp_basic_agent" , "basic/mcp_basic_agent" ),
318+ "token-counter" : (None , "token_counter" , "basic/token_counter" ),
319+ "agent-factory" : (None , "agent_factory" , "basic/agent_factory" ),
320+ "reference-agent-server" : (None , "reference_agent_server" , "mcp_agent_server/reference" ),
321+ "elicitation" : (None , "elicitation" , "mcp_agent_server/elicitation" ),
322+ "sampling" : (None , "sampling" , "mcp_agent_server/sampling" ),
323+ "notifications" : (None , "notifications" , "mcp_agent_server/notifications" ),
324+ "hello-world" : (EXAMPLE_ROOT / "cloud" / "hello_world" , "hello_world" ),
325+ "mcp" : (EXAMPLE_ROOT / "cloud" / "mcp" , "mcp" ),
326+ "temporal" : (EXAMPLE_ROOT / "cloud" / "temporal" , "temporal" ),
327+ "chatgpt-app" : (EXAMPLE_ROOT / "cloud" / "chatgpt_app" , "chatgpt_app" ),
328+ }
329+
330+ mapping = example_map .get (template )
331+ if not mapping :
332+ console .print (f"[red]Example template '{ template } ' not found[/red]" )
333+ raise typer .Exit (1 )
334+
335+ if len (mapping ) == 3 :
336+ _ , dst_name , pkg_rel = mapping
337+ dst = dir / dst_name
338+ copied = _copy_pkg_tree (pkg_rel , dst , force )
339+ if not copied :
340+ src = EXAMPLE_ROOT / pkg_rel .replace ("/" , "_" )
341+ if src .exists ():
342+ copied = _copy_tree (src , dst , force )
343+ else :
344+ src , dst_name = mapping
345+ dst = dir / dst_name
346+ copied = _copy_tree (src , dst , force )
347+
348+ if copied :
349+ console .print (f"\n [green]✅ Successfully copied example '{ template } '![/green]" )
350+ console .print (f"Created: [cyan]{ dst } [/cyan]\n " )
351+ console .print ("[bold]Next steps:[/bold]" )
352+ console .print (f"1. cd [cyan]{ dst } [/cyan]" )
353+ console .print ("2. Review the README for instructions" )
354+ console .print ("3. Add your API keys to config/secrets files if needed" )
355+ else :
356+ console .print (f"[yellow]Example '{ template } ' could not be copied[/yellow]" )
357+ console .print ("The destination may already exist. Use --force to overwrite." )
358+
359+ return
360+
154361 if template == "basic" :
155362 # Determine entry script name and handle existing files
156363 script_name = "main.py"
@@ -278,7 +485,7 @@ def init(
278485 console .print ("4. Run the factory: [cyan]uv run factory.py[/cyan]" )
279486 elif template == "minimal" :
280487 console .print ("3. Create your agent script" )
281- console .print (" See examples: [cyan]mcp-agent quickstart [/cyan]" )
488+ console .print (" See examples: [cyan]mcp-agent init --list [/cyan]" )
282489
283490 console .print (
284491 "\n [dim]Run [cyan]mcp-agent doctor[/cyan] to check your configuration[/dim]"
@@ -346,10 +553,12 @@ def interactive(
346553 console .print (f"\n [bold]Creating project '{ project_name } '...[/bold]" )
347554
348555 # Use the main init function with selected options
556+ ctx = typer .Context (init )
349557 init (
350- ctx = typer . Context () ,
558+ ctx = ctx ,
351559 dir = dir ,
352560 template = template_name ,
561+ quickstart = None ,
353562 force = False ,
354563 no_gitignore = False ,
355564 list_templates = False ,
0 commit comments