Skip to content

Commit 13ec7e6

Browse files
kargnasclaude[bot]claude
authored
feat: Add tag-based directory grouping for Swagger generator (#53)
* feat: Add tag-based directory grouping for Swagger generator - Modified MakeSwaggerMcpToolCommand to create tag-based directories (Tools/Pet/, Tools/Store/, etc.) - Updated MakeMcpToolCommand to support subdirectories and namespaces - Updated MakeMcpResourceCommand to support subdirectories and namespaces - Added createTagDirectory() method to convert tags to StudlyCase directory names - Tools and resources now organized by OpenAPI tags for better organization Co-authored-by: Sangrak Choi <[email protected]> * Fix styling * feat: Add comprehensive test coverage for tag-based directory grouping - Add MakeSwaggerMcpToolCommandTest.php with createTagDirectory() method tests - Add MakeMcpToolCommandTest.php with tag directory path and namespace tests - Extend MakeMcpResourceCommandTest.php with tag directory support tests - Add TagDirectoryEdgeCasesTest.php with comprehensive edge case coverage - Test special characters, unicode, empty tags, namespace collision prevention - Add integration tests for full swagger-to-tools generation with tag directories - Ensure backward compatibility and proper config registration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Sangrak Choi <[email protected]> * Fix styling * feat: Add path-based grouping option and remove backward compatibility - Add grouping strategy options: tag (default), path, none - Implement path-based directory grouping using first path segment - Remove all backward compatibility code (116 lines) - Deleted selectEndpoints(), selectByTag(), selectByPath(), selectIndividually() - Removed unused property - Clean up legacy endpoint selection logic - Update tests with comprehensive coverage for both grouping strategies - Add 8 new test cases for path-based grouping functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Sangrak Choi <[email protected]> * Fix styling * feat: Add interactive CLI prompts for grouping selection - Remove default value from --group-by option - Add getGroupingOption() method with interactive prompts - Prompt users to choose between tag, path, or none grouping - Default to tag-based grouping when no selection made - Add comprehensive test coverage for interactive behavior - Update existing tests to use new groupingMethod property Co-authored-by: Sangrak Choi <[email protected]> * Fix styling * feat: Add preview examples for grouping options in interactive CLI - Show real endpoint examples for each grouping strategy (tag, path, none) - Display directory structures with actual file paths before user selection - Add generateGroupingPreviews() method to parse swagger and create samples - Enhanced user experience with color-coded folder icons and clear formatting - Comprehensive test coverage for preview functionality - Limit previews to 6 items for clean CLI display 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Sangrak Choi <[email protected]> * Fix styling * fix(commands): improve special character handling and no-interaction mode - Fix MakeSwaggerMcpToolCommand to properly handle special characters in tag names - Convert special characters (/, ., @, -, _) to StudlyCase directory names - Default empty or whitespace-only tags to 'General' directory - Add proper --no-interaction mode support for automated testing - Fix command constructors to properly inject filesystem dependency - Improve auto-registration logic in programmatic and no-interaction modes - Fix MigrateToolsCommand backup handling in no-interaction mode 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * fix(commands): improve tool config registration and test reliability - Fix backslash escaping in MakeMcpToolCommand for proper class name registration - Add handling for empty tools array in config file - Update tests to use POST instead of GET for proper tool generation testing - Remove trailing whitespace in MakeSwaggerMcpToolCommand - All 135 tests now pass 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * fix: change command name to make:mcp-tools-from-swagger - Changed from make:swagger-mcp-tool to make:mcp-tools-from-swagger for clarity - Updated all tests to use the new command name - Command now clearly indicates it generates multiple tools from swagger * refactor: rename command class to match command name - Renamed MakeSwaggerMcpToolCommand to MakeMcpToolsFromSwaggerCommand - Renamed test file to MakeMcpToolsFromSwaggerCommandTest - Updated all references in tests and service provider - Better alignment between command name (make:mcp-tools-from-swagger) and class name * Revert "refactor: rename command class to match command name" This reverts commit 4129ee8. * Revert "fix: change command name to make:mcp-tools-from-swagger" This reverts commit 6c656ff. * docs: Update README.md with new grouping options for Swagger generator - Added documentation for --group-by option (tag, path, none) - Documented interactive grouping selection with preview - Updated v1.4.0 changelog to highlight grouping strategies - Added examples showing all three grouping methods - Enhanced key features list with organization strategies Co-authored-by: Sangrak Choi <[email protected]> * feat: enhance Swagger MCP generator with improved interactive preview - Replace "General/" folder with root directory for no-grouping option - Add comprehensive statistics in interactive preview (total endpoints, tools, resources) - Show directory structure with file counts per group - Display actual file examples with HTTP methods and paths - Add "... and X more files" indicators for large groups - Improve visual hierarchy with tree-style formatting - Update README.md with v1.4.2 enhancements documentation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * Fix styling --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Sangrak Choi <[email protected]> Co-authored-by: Claude <[email protected]> Co-authored-by: kargnas <[email protected]>
1 parent 537639c commit 13ec7e6

9 files changed

+1741
-201
lines changed

README.md

Lines changed: 97 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,27 @@ Version 1.4.0 introduces powerful automatic tool and resource generation from Sw
4040
- **Swagger/OpenAPI Tool & Resource Generator**: Automatically generate MCP tools or resources from any Swagger/OpenAPI specification
4141
- Supports both OpenAPI 3.x and Swagger 2.0 formats
4242
- **Choose generation type**: Generate as Tools (for actions) or Resources (for read-only data)
43-
- Interactive endpoint selection with grouping options
43+
- **Multiple grouping strategies** (v1.4.1): Organize by tags, paths, or root directory
44+
- **Enhanced interactive preview** (v1.4.2): Shows directory structure with file counts and examples
45+
- **Interactive endpoint selection** with real-time preview of directory structure
4446
- Automatic authentication logic generation (API Key, Bearer Token, OAuth2)
4547
- Smart naming for readable class names (handles hash-based operationIds)
4648
- Built-in API testing before generation
4749
- Complete Laravel HTTP client integration with retry logic
4850

4951
**Example Usage:**
5052
```bash
51-
# Generate tools from OP.GG API
53+
# Generate tools from OP.GG API (interactive mode)
5254
php artisan make:swagger-mcp-tool https://api.op.gg/lol/swagger.json
5355

54-
# With options
56+
# With specific grouping
5557
php artisan make:swagger-mcp-tool ./api-spec.json --test-api --group-by=tag --prefix=MyApi
58+
59+
# Group by path segments
60+
php artisan make:swagger-mcp-tool ./api-spec.json --group-by=path
61+
62+
# No grouping (flat structure)
63+
php artisan make:swagger-mcp-tool ./api-spec.json --group-by=none
5664
```
5765

5866
This feature dramatically reduces the time needed to integrate external APIs into your MCP server!
@@ -653,6 +661,86 @@ php artisan make:swagger-mcp-tool https://api.example.com/swagger.json \
653661
--prefix=MyApi
654662
```
655663

664+
**Grouping Options (v1.4.1+):**
665+
666+
The generator now supports multiple ways to organize your generated tools and resources into directories:
667+
668+
```bash
669+
# Tag-based grouping (default) - organize by OpenAPI tags
670+
php artisan make:swagger-mcp-tool petstore.json --group-by=tag
671+
# Creates: Tools/Pet/, Tools/Store/, Tools/User/
672+
673+
# Path-based grouping - organize by first path segment
674+
php artisan make:swagger-mcp-tool petstore.json --group-by=path
675+
# Creates: Tools/Api/, Tools/Users/, Tools/Orders/
676+
677+
# No grouping - everything in root directories
678+
php artisan make:swagger-mcp-tool petstore.json --group-by=none
679+
# Creates: Tools/, Resources/
680+
```
681+
682+
**Enhanced Interactive Preview (v1.4.2):**
683+
684+
When you don't specify the `--group-by` option, the command shows a detailed preview with statistics:
685+
686+
```bash
687+
php artisan make:swagger-mcp-tool petstore.json
688+
689+
🗂️ Choose how to organize your generated tools and resources:
690+
691+
Tag-based grouping (organize by OpenAPI tags)
692+
📊 Total: 25 endpoints → 15 tools + 10 resources
693+
694+
📁 Pet/ (8 tools, 4 resources)
695+
└─ CreatePetTool.php (POST /pet)
696+
└─ UpdatePetTool.php (PUT /pet)
697+
└─ ... and 10 more files
698+
📁 Store/ (5 tools, 3 resources)
699+
└─ PlaceOrderTool.php (POST /store/order)
700+
└─ GetInventoryResource.php (GET /store/inventory)
701+
└─ ... and 6 more files
702+
📁 User/ (2 tools, 3 resources)
703+
└─ CreateUserTool.php (POST /user)
704+
└─ GetUserByNameResource.php (GET /user/{username})
705+
└─ ... and 3 more files
706+
707+
Path-based grouping (organize by API path)
708+
📊 Total: 25 endpoints → 15 tools + 10 resources
709+
710+
📁 Pet/ (12 files from /pet)
711+
└─ PostPetTool.php (POST /pet)
712+
└─ GetPetByIdResource.php (GET /pet/{petId})
713+
└─ ... and 10 more files
714+
📁 Store/ (8 files from /store)
715+
└─ PostStoreOrderTool.php (POST /store/order)
716+
└─ GetStoreInventoryResource.php (GET /store/inventory)
717+
└─ ... and 6 more files
718+
719+
No grouping (everything in root folder)
720+
📊 Total: 25 endpoints → 15 tools + 10 resources
721+
722+
📁 Tools/ (15 files directly in root)
723+
└─ CreatePetTool.php (POST /pet)
724+
└─ UpdatePetTool.php (PUT /pet/{petId})
725+
└─ ... and 13 more files
726+
📁 Resources/ (10 files directly in root)
727+
└─ GetPetByIdResource.php (GET /pet/{petId})
728+
└─ GetStoreInventoryResource.php (GET /store/inventory)
729+
└─ ... and 8 more files
730+
731+
Choose grouping method:
732+
[0] Tag-based grouping
733+
[1] Path-based grouping
734+
[2] No grouping
735+
> 0
736+
```
737+
738+
The interactive preview shows:
739+
- **Total counts**: How many tools and resources will be generated
740+
- **Directory structure**: Actual directories that will be created
741+
- **File examples**: Sample files with their corresponding API endpoints
742+
- **File distribution**: Number of files per directory/group
743+
656744
**Real-world Example with OP.GG API:**
657745

658746
```bash
@@ -719,10 +807,14 @@ Generating: LolRegionServerStatsResource
719807
- **Resources**: For read-only GET endpoints that provide data
720808
- **Smart naming**: Converts paths like `/lol/{region}/server-stats` to `LolRegionServerStatsTool` or `LolRegionServerStatsResource`
721809
- **Hash detection**: Automatically detects MD5-like operationIds and uses path-based naming instead
722-
- **Interactive mode**: Select which endpoints to convert
810+
- **Interactive mode**: Select which endpoints to convert with real-time preview
723811
- **API testing**: Test API connectivity before generating
724812
- **Authentication support**: Automatically generates authentication logic for API Key, Bearer Token, and OAuth2
725-
- **Smart grouping**: Group endpoints by tags or path prefixes
813+
- **Flexible organization strategies**:
814+
- **Tag-based grouping**: Organize by OpenAPI tags (e.g., `Tools/Pet/`, `Tools/Store/`)
815+
- **Path-based grouping**: Organize by API path segments (e.g., `Tools/Api/`, `Tools/Users/`)
816+
- **Flat structure**: All tools in a single `General/` directory
817+
- **Interactive grouping preview**: See exactly how your files will be organized before generation
726818
- **Code generation**: Creates ready-to-use classes with Laravel HTTP client integration
727819

728820
The generated tools include:

src/Console/Commands/MakeMcpResourceCommand.php

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,23 +71,27 @@ public function handle()
7171

7272
$this->info("✅ Created: {$path}");
7373

74-
$fullClassName = "\\App\\MCP\\Resources\\{$className}";
75-
76-
// Ask if they want to automatically register the resource (skip in programmatic mode)
77-
if (! $this->option('programmatic')) {
78-
if ($this->confirm('🤖 Would you like to automatically register this resource in config/mcp-server.php?', true)) {
79-
$this->registerResourceInConfig($fullClassName);
80-
} else {
81-
$this->info("☑️ Don't forget to register your resource in config/mcp-server.php:");
82-
$this->comment(' // config/mcp-server.php');
83-
$this->comment(" 'resources' => [");
84-
$this->comment(' // other resources...');
85-
$this->comment(" {$fullClassName}::class,");
86-
$this->comment(' ],');
87-
}
88-
} else {
89-
// In programmatic mode, always register the resource
74+
// Build full class name with tag directory support
75+
$tagDirectory = $this->dynamicParams['tagDirectory'] ?? '';
76+
$fullClassName = '\\App\\MCP\\Resources\\';
77+
if ($tagDirectory) {
78+
$fullClassName .= "{$tagDirectory}\\";
79+
}
80+
$fullClassName .= $className;
81+
82+
// Ask if they want to automatically register the resource
83+
if ($this->option('programmatic') || $this->option('no-interaction')) {
84+
// In programmatic or no-interaction mode, always register automatically
85+
$this->registerResourceInConfig($fullClassName);
86+
} elseif ($this->confirm('🤖 Would you like to automatically register this resource in config/mcp-server.php?', true)) {
9087
$this->registerResourceInConfig($fullClassName);
88+
} else {
89+
$this->info("☑️ Don't forget to register your resource in config/mcp-server.php:");
90+
$this->comment(' // config/mcp-server.php');
91+
$this->comment(" 'resources' => [");
92+
$this->comment(' // other resources...');
93+
$this->comment(" {$fullClassName}::class,");
94+
$this->comment(' ],');
9195
}
9296

9397
return 0;
@@ -136,6 +140,13 @@ protected function getClassName()
136140
*/
137141
protected function getPath(string $className)
138142
{
143+
// Check if we have a tag directory from dynamic params
144+
$tagDirectory = $this->dynamicParams['tagDirectory'] ?? '';
145+
146+
if ($tagDirectory) {
147+
return app_path("MCP/Resources/{$tagDirectory}/{$className}.php");
148+
}
149+
139150
// Create the file in the app/MCP/Resources directory
140151
return app_path("MCP/Resources/{$className}.php");
141152
}
@@ -188,9 +199,16 @@ protected function buildDynamicClass(string $className): string
188199
$mimeType = $this->dynamicParams['mimeType'] ?? 'application/json';
189200
$readLogic = $this->dynamicParams['readLogic'] ?? $this->getDefaultReadLogic();
190201

202+
// Build namespace with tag directory support
203+
$namespace = 'App\\MCP\\Resources';
204+
$tagDirectory = $this->dynamicParams['tagDirectory'] ?? '';
205+
if ($tagDirectory) {
206+
$namespace .= '\\'.$tagDirectory;
207+
}
208+
191209
// Replace placeholders in stub
192210
$replacements = [
193-
'{{ namespace }}' => 'App\\MCP\\Resources',
211+
'{{ namespace }}' => $namespace,
194212
'{{ className }}' => $className,
195213
'{{ uri }}' => $uri,
196214
'{{ name }}' => addslashes($name),
@@ -249,9 +267,16 @@ protected function getStubPath()
249267
*/
250268
protected function replaceStubPlaceholders(string $stub, string $className)
251269
{
270+
// Build namespace with tag directory support
271+
$namespace = 'App\\MCP\\Resources';
272+
$tagDirectory = $this->dynamicParams['tagDirectory'] ?? '';
273+
if ($tagDirectory) {
274+
$namespace .= '\\'.$tagDirectory;
275+
}
276+
252277
return str_replace(
253278
['{{ className }}', '{{ namespace }}'],
254-
[$className, 'App\\MCP\\Resources'],
279+
[$className, $namespace],
255280
$stub
256281
);
257282
}
@@ -267,7 +292,9 @@ protected function registerResourceInConfig(string $resourceClassName): bool
267292
$configPath = config_path('mcp-server.php');
268293

269294
if (! file_exists($configPath)) {
270-
$this->error("❌ Config file not found: {$configPath}");
295+
if (property_exists($this, 'output') && $this->output) {
296+
$this->error("❌ Config file not found: {$configPath}");
297+
}
271298

272299
return false;
273300
}
@@ -283,17 +310,23 @@ protected function registerResourceInConfig(string $resourceClassName): bool
283310
$newContent = str_replace($toolsArray, "{$toolsArray}{$resourcesArray}", $content);
284311

285312
if (file_put_contents($configPath, $newContent)) {
286-
$this->info('✅ Created resources array and registered resource in config/mcp-server.php');
313+
if (property_exists($this, 'output') && $this->output) {
314+
$this->info('✅ Created resources array and registered resource in config/mcp-server.php');
315+
}
287316

288317
return true;
289318
} else {
290-
$this->error('❌ Failed to update config file. Please manually register the resource.');
319+
if (property_exists($this, 'output') && $this->output) {
320+
$this->error('❌ Failed to update config file. Please manually register the resource.');
321+
}
291322

292323
return false;
293324
}
294325
}
295326

296-
$this->error('❌ Could not locate resources array in config file.');
327+
if (property_exists($this, 'output') && $this->output) {
328+
$this->error('❌ Could not locate resources array in config file.');
329+
}
297330

298331
return false;
299332
}
@@ -302,7 +335,9 @@ protected function registerResourceInConfig(string $resourceClassName): bool
302335

303336
// Check if the resource is already registered
304337
if (strpos($resourcesArrayContent, $resourceClassName) !== false) {
305-
$this->info('✅ Resource is already registered in config file.');
338+
if (property_exists($this, 'output') && $this->output) {
339+
$this->info('✅ Resource is already registered in config file.');
340+
}
306341

307342
return true;
308343
}
@@ -319,11 +354,15 @@ protected function registerResourceInConfig(string $resourceClassName): bool
319354

320355
// Write the updated content back to the config file
321356
if (file_put_contents($configPath, $newContent)) {
322-
$this->info('✅ Resource registered successfully in config/mcp-server.php');
357+
if (property_exists($this, 'output') && $this->output) {
358+
$this->info('✅ Resource registered successfully in config/mcp-server.php');
359+
}
323360

324361
return true;
325362
} else {
326-
$this->error('❌ Failed to update config file. Please manually register the resource.');
363+
if (property_exists($this, 'output') && $this->output) {
364+
$this->error('❌ Failed to update config file. Please manually register the resource.');
365+
}
327366

328367
return false;
329368
}

0 commit comments

Comments
 (0)