Skip to content

Commit efa63e3

Browse files
author
klapaudius
committed
Add `SchemaBuilder` for complex JSON schema creation and introduce `SearchResultsTool` example with tests.
1 parent 3bcb4df commit efa63e3

File tree

9 files changed

+479
-3
lines changed

9 files changed

+479
-3
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Instructions: `pre-commit-review`
2+
3+
Please perform a comprehensive code review for the uncommited changes. Follow these steps:
4+
5+
### 1. Code Quality Review
6+
Analyze all changes and evaluate:
7+
8+
**Architecture & Design:**
9+
- Does the code follow Symfony >6.4 best practices and conventions?
10+
- Does the code follow single responsibility principle with clear separation of concerns?
11+
- Are new dependencies justified and properly integrated?
12+
13+
**Security & Performance:**
14+
- Are there any security vulnerabilities or exposed sensitive data?
15+
- Is caching implemented where beneficial?
16+
- Does memory usage is optimal?
17+
18+
**Code Standards:**
19+
- Does the code follow PSR standards and project conventions?
20+
- Are variable names and methods descriptive and consistent?
21+
- Is error handling comprehensive and appropriate?
22+
- Are comments and documentation adequate?
23+
- Does all methods have a PHPDocs block?
24+
- Does test method's names are snake_case_named?
25+
26+
**Testing & Reliability:**
27+
- Are unit tests present for the new functionality?
28+
- Do integration tests cover the main workflows?
29+
- Are edge cases and error scenarios tested?
30+
- Is the test coverage adequate?
31+
32+
### 2. Risk Assessment
33+
Identify potential risks:
34+
- Breaking changes that might affect existing functionality
35+
- Database migration requirements
36+
- Performance impact on large datasets
37+
- Compatibility issues with external integrations
38+
39+
### 3. Recommendations
40+
Provide specific, actionable recommendations:
41+
- Code improvements and refactoring suggestions
42+
- Missing tests or documentation
43+
- Performance optimizations
44+
- Security enhancements
45+
46+
### 4. Approval Decision
47+
Based on the analysis, provide one of:
48+
-  **APPROVED** - Ready to merge
49+
- � **APPROVED WITH MINOR CHANGES** - Can merge after addressing minor issues
50+
- L **NEEDS WORK** - Requires significant changes before merge
51+
52+
Include a summary of the most critical issues that must be addressed before merging.
53+
54+
---
55+
56+
*This review should focus on maintainability, security, performance, and adherence to the Symfony best practices.*

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ class SupportAgent implements SamplingAwareToolInterface
295295
- KLP\KlpMcpServer\Services\ToolService\Examples\CodeAnalyzerTool # Agentic tool sample
296296
- KLP\KlpMcpServer\Services\ToolService\Examples\HelloWorldTool
297297
- KLP\KlpMcpServer\Services\ToolService\Examples\ProfileGeneratorTool
298+
- KLP\KlpMcpServer\Services\ToolService\Examples\SearchResultsTool
298299
- KLP\KlpMcpServer\Services\ToolService\Examples\StreamingDataTool
299300
- KLP\KlpMcpServer\Services\ToolService\Examples\VersionCheckTool
300301
prompts:

docs/building_tools.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ public function getInputSchema(): StructuredSchema
183183
}
184184
```
185185

186-
The `PropertyType` enum provides a list of supported data types: `STRING`, `INTEGER`
186+
The `PropertyType` enum provides a list of supported data types: `STRING`, `INTEGER`, `BOOLEAN`, `NUMBER`, `ARRAY`, `OBJECT`
187187

188188
### 4. getOutputSchema(): ?StructuredSchema
189189

src/Resources/config/packages/klp_mcp_server.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ klp_mcp_server:
7171
- KLP\KlpMcpServer\Services\ToolService\Examples\CodeAnalyzerTool # Agentic Tool Sample
7272
- KLP\KlpMcpServer\Services\ToolService\Examples\HelloWorldTool
7373
- KLP\KlpMcpServer\Services\ToolService\Examples\ProfileGeneratorTool
74+
- KLP\KlpMcpServer\Services\ToolService\Examples\SearchResultsTool
7475
- KLP\KlpMcpServer\Services\ToolService\Examples\StreamingDataTool
7576
- KLP\KlpMcpServer\Services\ToolService\Examples\VersionCheckTool
7677

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
3+
namespace KLP\KlpMcpServer\Services\ToolService\Examples;
4+
5+
use KLP\KlpMcpServer\Services\ToolService\Annotation\ToolAnnotation;
6+
use KLP\KlpMcpServer\Services\ToolService\BaseToolInterface;
7+
use KLP\KlpMcpServer\Services\ToolService\Result\TextToolResult;
8+
use KLP\KlpMcpServer\Services\ToolService\Result\ToolResultInterface;
9+
use KLP\KlpMcpServer\Services\ToolService\Schema\PropertyType;
10+
use KLP\KlpMcpServer\Services\ToolService\Schema\SchemaBuilder;
11+
use KLP\KlpMcpServer\Services\ToolService\Schema\SchemaProperty;
12+
use KLP\KlpMcpServer\Services\ToolService\Schema\StructuredSchema;
13+
14+
/**
15+
* Example tool demonstrating how to create outputSchema with arrays of objects.
16+
*
17+
* This tool shows how to use the enhanced SchemaProperty and SchemaBuilder
18+
* to create complex JSON Schema structures that include arrays of objects.
19+
*/
20+
class SearchResultsTool implements BaseToolInterface
21+
{
22+
public function getName(): string
23+
{
24+
return 'search_results';
25+
}
26+
27+
public function getDescription(): string
28+
{
29+
return 'Example tool that returns search results in a structured array format';
30+
}
31+
32+
public function getInputSchema(): StructuredSchema
33+
{
34+
return new StructuredSchema(
35+
new SchemaProperty(
36+
name: 'query',
37+
type: PropertyType::STRING,
38+
description: 'Search query to process',
39+
required: true
40+
),
41+
new SchemaProperty(
42+
name: 'limit',
43+
type: PropertyType::INTEGER,
44+
description: 'Maximum number of results to return',
45+
default: '10'
46+
)
47+
);
48+
}
49+
50+
public function getOutputSchema(): ?StructuredSchema
51+
{
52+
return new StructuredSchema(
53+
// Using SchemaBuilder for array of objects
54+
SchemaBuilder::arrayOfObjects(
55+
'results',
56+
[
57+
'id' => ['type' => 'string'],
58+
'title' => ['type' => 'string'],
59+
'url' => ['type' => 'string'],
60+
'snippet' => ['type' => 'string'],
61+
'score' => ['type' => 'number']
62+
],
63+
['id', 'title'], // id and title are required
64+
'Array of search results'
65+
),
66+
67+
// Additional metadata object
68+
SchemaBuilder::nestedObject(
69+
'metadata',
70+
[
71+
'totalResults' => ['type' => 'integer'],
72+
'searchTime' => ['type' => 'number'],
73+
'query' => ['type' => 'string']
74+
],
75+
['totalResults', 'query'],
76+
'Search metadata'
77+
),
78+
79+
// Array of suggestion strings
80+
SchemaBuilder::arrayOfPrimitives(
81+
'suggestions',
82+
'string',
83+
'Alternative search suggestions'
84+
)
85+
);
86+
}
87+
88+
public function execute(array $arguments): ToolResultInterface
89+
{
90+
$query = $arguments['query'];
91+
$limit = $arguments['limit'] ?? 10;
92+
93+
// Simulate search results
94+
$results = [];
95+
for ($i = 1; $i <= min($limit, 5); $i++) {
96+
$results[] = [
97+
'id' => "result_$i",
98+
'title' => "Search Result $i for: $query",
99+
'url' => "https://example.com/result/$i",
100+
'snippet' => "This is a snippet for result $i matching your search query.",
101+
'score' => 1.0 - ($i * 0.1)
102+
];
103+
}
104+
105+
$response = [
106+
'results' => $results,
107+
'metadata' => [
108+
'totalResults' => count($results),
109+
'searchTime' => 0.125,
110+
'query' => $query
111+
],
112+
'suggestions' => [
113+
"$query tips",
114+
"$query tutorial",
115+
"$query examples"
116+
]
117+
];
118+
119+
return new TextToolResult(json_encode($response, JSON_PRETTY_PRINT));
120+
}
121+
122+
public function getAnnotations(): ToolAnnotation
123+
{
124+
return new ToolAnnotation();
125+
}
126+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
3+
namespace KLP\KlpMcpServer\Services\ToolService\Schema;
4+
5+
/**
6+
* Builder class for creating complex JSON Schema structures with StructuredSchema.
7+
*
8+
* This class provides convenient methods for creating arrays of objects,
9+
* nested objects, and other complex schema patterns that are common in MCP tools.
10+
*/
11+
class SchemaBuilder
12+
{
13+
/**
14+
* Creates an array property that contains objects with the specified properties.
15+
*
16+
* Example usage:
17+
* ```php
18+
* SchemaBuilder::arrayOfObjects('results', [
19+
* 'id' => ['type' => 'string'],
20+
* 'title' => ['type' => 'string'],
21+
* 'url' => ['type' => 'string']
22+
* ], ['id', 'title'])
23+
* ```
24+
*
25+
* @param string $name Property name
26+
* @param array<string, mixed> $objectProperties Properties of objects in the array
27+
* @param array<string> $requiredProperties Required properties in each object
28+
* @param string $description Property description
29+
* @param bool $required Whether the array property itself is required
30+
* @return SchemaProperty
31+
*/
32+
public static function arrayOfObjects(
33+
string $name,
34+
array $objectProperties,
35+
array $requiredProperties = [],
36+
string $description = '',
37+
bool $required = false
38+
): SchemaProperty {
39+
$items = [
40+
'type' => 'object',
41+
'properties' => $objectProperties,
42+
];
43+
44+
if (!empty($requiredProperties)) {
45+
$items['required'] = $requiredProperties;
46+
}
47+
48+
return new SchemaProperty(
49+
name: $name,
50+
type: PropertyType::ARRAY,
51+
description: $description,
52+
required: $required,
53+
items: $items
54+
);
55+
}
56+
57+
/**
58+
* Creates an array property that contains primitive values.
59+
*
60+
* @param string $name Property name
61+
* @param string $itemType Type of items in the array ('string', 'integer', 'number', 'boolean')
62+
* @param string $description Property description
63+
* @param bool $required Whether the array property is required
64+
* @return SchemaProperty
65+
*/
66+
public static function arrayOfPrimitives(
67+
string $name,
68+
string $itemType,
69+
string $description = '',
70+
bool $required = false
71+
): SchemaProperty {
72+
return new SchemaProperty(
73+
name: $name,
74+
type: PropertyType::ARRAY,
75+
description: $description,
76+
required: $required,
77+
items: ['type' => $itemType]
78+
);
79+
}
80+
81+
/**
82+
* Creates an object property with nested properties.
83+
*
84+
* @param string $name Property name
85+
* @param array<string, mixed> $properties Nested object properties
86+
* @param array<string> $requiredProperties Required nested properties
87+
* @param string $description Property description
88+
* @param bool $required Whether the object property is required
89+
* @return SchemaProperty
90+
*/
91+
public static function nestedObject(
92+
string $name,
93+
array $properties,
94+
array $requiredProperties = [],
95+
string $description = '',
96+
bool $required = false
97+
): SchemaProperty {
98+
$objectSchema = ['properties' => $properties];
99+
100+
if (!empty($requiredProperties)) {
101+
$objectSchema['required'] = $requiredProperties;
102+
}
103+
104+
return new SchemaProperty(
105+
name: $name,
106+
type: PropertyType::OBJECT,
107+
description: $description,
108+
required: $required,
109+
properties: $objectSchema
110+
);
111+
}
112+
}

src/Services/ToolService/Schema/SchemaProperty.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,20 @@
1919
* @param array $enum An array of allowed values for this property
2020
* @param string $default The default value for this property
2121
* @param bool $required Whether this property is required in the input schema
22+
* @param array|null $items For array types: defines the schema of array items
23+
* @param array|null $properties For object types: defines the nested object properties
24+
* @param array $additionalProperties Additional JSON Schema properties (e.g., minLength, maxLength, format)
2225
*/
2326
public function __construct(
2427
private string $name,
2528
private PropertyType $type,
2629
private string $description = '',
2730
private array $enum = [],
2831
private string $default = '',
29-
private bool $required = false
32+
private bool $required = false,
33+
private array|null $items = null,
34+
private array|null $properties = null,
35+
private array $additionalProperties = []
3036
) {}
3137

3238
/**
@@ -78,4 +84,34 @@ public function getDefault(): string
7884
{
7985
return $this->default;
8086
}
87+
88+
/**
89+
* Gets the items schema for array properties.
90+
*
91+
* @return array|null The items schema definition, or null if not applicable
92+
*/
93+
public function getItems(): array|null
94+
{
95+
return $this->items;
96+
}
97+
98+
/**
99+
* Gets the properties schema for object properties.
100+
*
101+
* @return array|null The nested properties definition, or null if not applicable
102+
*/
103+
public function getProperties(): array|null
104+
{
105+
return $this->properties;
106+
}
107+
108+
/**
109+
* Gets additional JSON Schema properties.
110+
*
111+
* @return array Additional properties like minLength, maxLength, format, etc.
112+
*/
113+
public function getAdditionalProperties(): array
114+
{
115+
return $this->additionalProperties;
116+
}
81117
}

0 commit comments

Comments
 (0)