@@ -670,6 +670,97 @@ public function devProfile(array $options = ['skip-modules' => FALSE]): void {
670670 $ this ->io ()->text (' 3) Run drush mcp:status to verify configuration ' );
671671 }
672672
673+ /**
674+ * Generate MCP client configuration JSON for AI editors.
675+ */
676+ #[CLI \Command(name: 'mcp-tools:client-config ' , aliases: ['mcp-client-config ' ])]
677+ #[CLI \Usage(name: 'drush mcp-tools:client-config ' , description: 'Generate MCP client config JSON ' )]
678+ #[CLI \Usage(name: 'drush mcp-tools:client-config --scope=read ' , description: 'Generate read-only config ' )]
679+ #[CLI \Usage(name: 'drush mcp-tools:client-config > .mcp.json ' , description: 'Save config directly to file ' )]
680+ #[CLI \Option(name: 'scope ' , description: 'Scopes for the MCP server (default: read,write) ' )]
681+ #[CLI \Option(name: 'uid ' , description: 'Drupal user ID for tool execution (default: 1) ' )]
682+ public function clientConfig (array $ options = ['scope ' => 'read,write ' , 'uid ' => '1 ' ]): void {
683+ $ scope = is_string ($ options ['scope ' ]) ? $ options ['scope ' ] : 'read,write ' ;
684+ $ uid = is_string ($ options ['uid ' ]) ? $ options ['uid ' ] : '1 ' ;
685+
686+ $ drupalRoot = \Drupal::root ();
687+ $ isDdev = (bool ) getenv ('IS_DDEV_PROJECT ' );
688+ $ isLando = (bool ) getenv ('LANDO ' );
689+
690+ if ($ isDdev ) {
691+ // Inside DDEV: the project root is one level above the Drupal root
692+ // (web/ docroot), or if Drupal IS the project root, use that.
693+ $ projectRoot = dirname ($ drupalRoot );
694+ if (basename ($ drupalRoot ) === $ drupalRoot ) {
695+ $ projectRoot = $ drupalRoot ;
696+ }
697+ $ config = [
698+ 'mcpServers ' => [
699+ 'drupal ' => [
700+ 'command ' => 'ddev ' ,
701+ 'args ' => [
702+ 'drush ' ,
703+ 'mcp-tools:serve ' ,
704+ '--quiet ' ,
705+ "--uid= {$ uid }" ,
706+ "--scope= {$ scope }" ,
707+ ],
708+ 'cwd ' => $ projectRoot ,
709+ ],
710+ ],
711+ ];
712+ }
713+ elseif ($ isLando ) {
714+ $ projectRoot = dirname ($ drupalRoot );
715+ if (basename ($ drupalRoot ) === $ drupalRoot ) {
716+ $ projectRoot = $ drupalRoot ;
717+ }
718+ $ config = [
719+ 'mcpServers ' => [
720+ 'drupal ' => [
721+ 'command ' => 'lando ' ,
722+ 'args ' => [
723+ 'drush ' ,
724+ 'mcp-tools:serve ' ,
725+ '--quiet ' ,
726+ "--uid= {$ uid }" ,
727+ "--scope= {$ scope }" ,
728+ ],
729+ 'cwd ' => $ projectRoot ,
730+ ],
731+ ],
732+ ];
733+ }
734+ else {
735+ // Bare metal / native.
736+ $ drushPath = $ drupalRoot . '/vendor/bin/drush ' ;
737+ $ config = [
738+ 'mcpServers ' => [
739+ 'drupal ' => [
740+ 'command ' => $ drushPath ,
741+ 'args ' => [
742+ 'mcp-tools:serve ' ,
743+ '--quiet ' ,
744+ "--uid= {$ uid }" ,
745+ "--scope= {$ scope }" ,
746+ ],
747+ 'cwd ' => $ drupalRoot ,
748+ ],
749+ ],
750+ ];
751+ }
752+
753+ // JSON to stdout (pipeable to file).
754+ $ this ->output ()->write (json_encode ($ config , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) . "\n" );
755+
756+ // Instructions to stderr (not captured when piping).
757+ fwrite (\STDERR , "\nSave this as one of: \n" );
758+ fwrite (\STDERR , " Claude Code: .mcp.json (project root) \n" );
759+ fwrite (\STDERR , " Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json \n" );
760+ fwrite (\STDERR , " Cursor: .cursor/mcp.json (project root) \n" );
761+ fwrite (\STDERR , " Windsurf: .windsurf/mcp.json (project root) \n" );
762+ }
763+
673764 /**
674765 * Count MCP tools from the tool manager.
675766 *
0 commit comments