Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e560c04
feat: Add tag-based directory grouping for Swagger generator
claude[bot] Aug 8, 2025
d379625
Fix styling
claude[bot] Aug 8, 2025
d58c77d
feat: Add comprehensive test coverage for tag-based directory grouping
claude[bot] Aug 8, 2025
b68a236
Fix styling
claude[bot] Aug 8, 2025
f6b77fe
feat: Add path-based grouping option and remove backward compatibility
claude[bot] Aug 9, 2025
ed8f875
Fix styling
claude[bot] Aug 9, 2025
b264bb9
feat: Add interactive CLI prompts for grouping selection
claude[bot] Aug 9, 2025
c3812ca
Fix styling
claude[bot] Aug 9, 2025
d24a8f8
feat: Add preview examples for grouping options in interactive CLI
claude[bot] Aug 9, 2025
b8f28b0
Fix styling
claude[bot] Aug 9, 2025
a27d76e
fix(commands): improve special character handling and no-interaction …
kargnas Aug 9, 2025
a961525
fix(commands): improve tool config registration and test reliability
kargnas Aug 9, 2025
8475704
Merge PR #53: Add grouping options for Swagger tool generation
kargnas Aug 9, 2025
6c656ff
fix: change command name to make:mcp-tools-from-swagger
kargnas Aug 9, 2025
4129ee8
refactor: rename command class to match command name
kargnas Aug 9, 2025
953b539
Revert "refactor: rename command class to match command name"
kargnas Aug 9, 2025
99f6860
Revert "fix: change command name to make:mcp-tools-from-swagger"
kargnas Aug 9, 2025
7b3ebdd
docs: Update README.md with new grouping options for Swagger generator
claude[bot] Aug 9, 2025
6c5e47c
feat: enhance Swagger MCP generator with improved interactive preview
kargnas Aug 9, 2025
ac4639e
Fix styling
kargnas Aug 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 97 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,27 @@ Version 1.4.0 introduces powerful automatic tool and resource generation from Sw
- **Swagger/OpenAPI Tool & Resource Generator**: Automatically generate MCP tools or resources from any Swagger/OpenAPI specification
- Supports both OpenAPI 3.x and Swagger 2.0 formats
- **Choose generation type**: Generate as Tools (for actions) or Resources (for read-only data)
- Interactive endpoint selection with grouping options
- **Multiple grouping strategies** (v1.4.1): Organize by tags, paths, or root directory
- **Enhanced interactive preview** (v1.4.2): Shows directory structure with file counts and examples
- **Interactive endpoint selection** with real-time preview of directory structure
- Automatic authentication logic generation (API Key, Bearer Token, OAuth2)
- Smart naming for readable class names (handles hash-based operationIds)
- Built-in API testing before generation
- Complete Laravel HTTP client integration with retry logic

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

# With options
# With specific grouping
php artisan make:swagger-mcp-tool ./api-spec.json --test-api --group-by=tag --prefix=MyApi

# Group by path segments
php artisan make:swagger-mcp-tool ./api-spec.json --group-by=path

# No grouping (flat structure)
php artisan make:swagger-mcp-tool ./api-spec.json --group-by=none
```

This feature dramatically reduces the time needed to integrate external APIs into your MCP server!
Expand Down Expand Up @@ -353,6 +361,86 @@ php artisan make:swagger-mcp-tool https://api.example.com/swagger.json \
--prefix=MyApi
```

**Grouping Options (v1.4.1+):**

The generator now supports multiple ways to organize your generated tools and resources into directories:

```bash
# Tag-based grouping (default) - organize by OpenAPI tags
php artisan make:swagger-mcp-tool petstore.json --group-by=tag
# Creates: Tools/Pet/, Tools/Store/, Tools/User/

# Path-based grouping - organize by first path segment
php artisan make:swagger-mcp-tool petstore.json --group-by=path
# Creates: Tools/Api/, Tools/Users/, Tools/Orders/

# No grouping - everything in root directories
php artisan make:swagger-mcp-tool petstore.json --group-by=none
# Creates: Tools/, Resources/
```

**Enhanced Interactive Preview (v1.4.2):**

When you don't specify the `--group-by` option, the command shows a detailed preview with statistics:

```bash
php artisan make:swagger-mcp-tool petstore.json

🗂️ Choose how to organize your generated tools and resources:

Tag-based grouping (organize by OpenAPI tags)
📊 Total: 25 endpoints → 15 tools + 10 resources

📁 Pet/ (8 tools, 4 resources)
└─ CreatePetTool.php (POST /pet)
└─ UpdatePetTool.php (PUT /pet)
└─ ... and 10 more files
📁 Store/ (5 tools, 3 resources)
└─ PlaceOrderTool.php (POST /store/order)
└─ GetInventoryResource.php (GET /store/inventory)
└─ ... and 6 more files
📁 User/ (2 tools, 3 resources)
└─ CreateUserTool.php (POST /user)
└─ GetUserByNameResource.php (GET /user/{username})
└─ ... and 3 more files

Path-based grouping (organize by API path)
📊 Total: 25 endpoints → 15 tools + 10 resources

📁 Pet/ (12 files from /pet)
└─ PostPetTool.php (POST /pet)
└─ GetPetByIdResource.php (GET /pet/{petId})
└─ ... and 10 more files
📁 Store/ (8 files from /store)
└─ PostStoreOrderTool.php (POST /store/order)
└─ GetStoreInventoryResource.php (GET /store/inventory)
└─ ... and 6 more files

No grouping (everything in root folder)
📊 Total: 25 endpoints → 15 tools + 10 resources

📁 Tools/ (15 files directly in root)
└─ CreatePetTool.php (POST /pet)
└─ UpdatePetTool.php (PUT /pet/{petId})
└─ ... and 13 more files
📁 Resources/ (10 files directly in root)
└─ GetPetByIdResource.php (GET /pet/{petId})
└─ GetStoreInventoryResource.php (GET /store/inventory)
└─ ... and 8 more files

Choose grouping method:
[0] Tag-based grouping
[1] Path-based grouping
[2] No grouping
> 0
```

The interactive preview shows:
- **Total counts**: How many tools and resources will be generated
- **Directory structure**: Actual directories that will be created
- **File examples**: Sample files with their corresponding API endpoints
- **File distribution**: Number of files per directory/group

**Real-world Example with OP.GG API:**

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

The generated tools include:
Expand Down
89 changes: 64 additions & 25 deletions src/Console/Commands/MakeMcpResourceCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,23 +71,27 @@ public function handle()

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

$fullClassName = "\\App\\MCP\\Resources\\{$className}";

// Ask if they want to automatically register the resource (skip in programmatic mode)
if (! $this->option('programmatic')) {
if ($this->confirm('🤖 Would you like to automatically register this resource in config/mcp-server.php?', true)) {
$this->registerResourceInConfig($fullClassName);
} else {
$this->info("☑️ Don't forget to register your resource in config/mcp-server.php:");
$this->comment(' // config/mcp-server.php');
$this->comment(" 'resources' => [");
$this->comment(' // other resources...');
$this->comment(" {$fullClassName}::class,");
$this->comment(' ],');
}
} else {
// In programmatic mode, always register the resource
// Build full class name with tag directory support
$tagDirectory = $this->dynamicParams['tagDirectory'] ?? '';
$fullClassName = '\\App\\MCP\\Resources\\';
if ($tagDirectory) {
$fullClassName .= "{$tagDirectory}\\";
}
$fullClassName .= $className;

// Ask if they want to automatically register the resource
if ($this->option('programmatic') || $this->option('no-interaction')) {
// In programmatic or no-interaction mode, always register automatically
$this->registerResourceInConfig($fullClassName);
} elseif ($this->confirm('🤖 Would you like to automatically register this resource in config/mcp-server.php?', true)) {
$this->registerResourceInConfig($fullClassName);
} else {
$this->info("☑️ Don't forget to register your resource in config/mcp-server.php:");
$this->comment(' // config/mcp-server.php');
$this->comment(" 'resources' => [");
$this->comment(' // other resources...');
$this->comment(" {$fullClassName}::class,");
$this->comment(' ],');
}

return 0;
Expand Down Expand Up @@ -136,6 +140,13 @@ protected function getClassName()
*/
protected function getPath(string $className)
{
// Check if we have a tag directory from dynamic params
$tagDirectory = $this->dynamicParams['tagDirectory'] ?? '';

if ($tagDirectory) {
return app_path("MCP/Resources/{$tagDirectory}/{$className}.php");
}

// Create the file in the app/MCP/Resources directory
return app_path("MCP/Resources/{$className}.php");
}
Expand Down Expand Up @@ -188,9 +199,16 @@ protected function buildDynamicClass(string $className): string
$mimeType = $this->dynamicParams['mimeType'] ?? 'application/json';
$readLogic = $this->dynamicParams['readLogic'] ?? $this->getDefaultReadLogic();

// Build namespace with tag directory support
$namespace = 'App\\MCP\\Resources';
$tagDirectory = $this->dynamicParams['tagDirectory'] ?? '';
if ($tagDirectory) {
$namespace .= '\\'.$tagDirectory;
}

// Replace placeholders in stub
$replacements = [
'{{ namespace }}' => 'App\\MCP\\Resources',
'{{ namespace }}' => $namespace,
'{{ className }}' => $className,
'{{ uri }}' => $uri,
'{{ name }}' => addslashes($name),
Expand Down Expand Up @@ -249,9 +267,16 @@ protected function getStubPath()
*/
protected function replaceStubPlaceholders(string $stub, string $className)
{
// Build namespace with tag directory support
$namespace = 'App\\MCP\\Resources';
$tagDirectory = $this->dynamicParams['tagDirectory'] ?? '';
if ($tagDirectory) {
$namespace .= '\\'.$tagDirectory;
}

return str_replace(
['{{ className }}', '{{ namespace }}'],
[$className, 'App\\MCP\\Resources'],
[$className, $namespace],
$stub
);
}
Expand All @@ -267,7 +292,9 @@ protected function registerResourceInConfig(string $resourceClassName): bool
$configPath = config_path('mcp-server.php');

if (! file_exists($configPath)) {
$this->error("❌ Config file not found: {$configPath}");
if (property_exists($this, 'output') && $this->output) {
$this->error("❌ Config file not found: {$configPath}");
}

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

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

return true;
} else {
$this->error('❌ Failed to update config file. Please manually register the resource.');
if (property_exists($this, 'output') && $this->output) {
$this->error('❌ Failed to update config file. Please manually register the resource.');
}

return false;
}
}

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

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

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

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

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

return true;
} else {
$this->error('❌ Failed to update config file. Please manually register the resource.');
if (property_exists($this, 'output') && $this->output) {
$this->error('❌ Failed to update config file. Please manually register the resource.');
}

return false;
}
Expand Down
Loading