Skip to content

Commit e8925f6

Browse files
authored
Fix invalid output schema for tool result/defaultValue fields (#63)
* Fix invalid output schema for tool result/defaultValue fields * Add JSON schema validation tests for inputSchema/outputSchema
1 parent 8435128 commit e8925f6

13 files changed

Lines changed: 432 additions & 73 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Updated `mcp/sdk` to 0.5.
66
- Surfaced nested SegmentEditor validation messages through MCP tool errors instead of collapsing them to the generic failure message.
7+
- Aligned the declared MCP `outputSchema` for the `matomo_api_*` tools so the `result` and `defaultValue` entries serialize as `{}` rather than `[]`, matching the MCP requirement that every schema value is a JSON object.
78

89
### 5.0.3
910

McpTools/ApiCallCreate.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ protected function init(): void
3636
openWorldHint: false,
3737
);
3838
$this->inputSchema = ApiCallToolInputSchema::SCHEMA;
39-
$this->outputSchema = ApiCallToolOutputSchema::ITEM;
39+
$this->outputSchema = ApiCallToolOutputSchema::item();
4040
}
4141

4242
public function shouldRegister(): bool

McpTools/ApiCallDelete.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ protected function init(): void
3636
openWorldHint: false,
3737
);
3838
$this->inputSchema = ApiCallToolInputSchema::SCHEMA;
39-
$this->outputSchema = ApiCallToolOutputSchema::ITEM;
39+
$this->outputSchema = ApiCallToolOutputSchema::item();
4040
}
4141

4242
public function shouldRegister(): bool

McpTools/ApiCallFull.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ protected function init(): void
3636
openWorldHint: false,
3737
);
3838
$this->inputSchema = ApiCallToolInputSchema::SCHEMA;
39-
$this->outputSchema = ApiCallToolOutputSchema::ITEM;
39+
$this->outputSchema = ApiCallToolOutputSchema::item();
4040
}
4141

4242
public function shouldRegister(): bool

McpTools/ApiCallRead.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ protected function init(): void
3535
openWorldHint: false,
3636
);
3737
$this->inputSchema = ApiCallToolInputSchema::SCHEMA;
38-
$this->outputSchema = ApiCallToolOutputSchema::ITEM;
38+
$this->outputSchema = ApiCallToolOutputSchema::item();
3939
}
4040

4141
public function shouldRegister(): bool

McpTools/ApiCallUpdate.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ protected function init(): void
3636
openWorldHint: false,
3737
);
3838
$this->inputSchema = ApiCallToolInputSchema::SCHEMA;
39-
$this->outputSchema = ApiCallToolOutputSchema::ITEM;
39+
$this->outputSchema = ApiCallToolOutputSchema::item();
4040
}
4141

4242
public function shouldRegister(): bool

McpTools/ApiGet.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ protected function init(): void
4747
openWorldHint: false,
4848
);
4949
$this->inputSchema = ApiGetToolInputSchema::SCHEMA;
50-
$this->outputSchema = ApiMethodSummaryToolOutputSchema::ITEM;
50+
$this->outputSchema = ApiMethodSummaryToolOutputSchema::item();
5151
}
5252

5353
public function shouldRegister(): bool

McpTools/ApiList.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ protected function init(): void
5252
openWorldHint: false,
5353
);
5454
$this->inputSchema = ApiListToolInputSchema::SCHEMA;
55-
$this->outputSchema = ApiMethodSummaryToolOutputSchema::PAGINATED_LIST;
55+
$this->outputSchema = ApiMethodSummaryToolOutputSchema::paginatedList();
5656
}
5757

5858
public function shouldRegister(): bool

Schemas/Api/ApiCallToolOutputSchema.php

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,21 @@
1313

1414
final class ApiCallToolOutputSchema
1515
{
16-
public const ITEM = [
17-
'type' => 'object',
18-
'properties' => [
19-
'result' => [],
20-
'resolvedMethod' => ApiMethodSummaryToolOutputSchema::ITEM,
21-
],
22-
'required' => ['result', 'resolvedMethod'],
23-
'additionalProperties' => false,
24-
];
16+
/**
17+
* @return array<string, mixed>
18+
*/
19+
public static function item(): array
20+
{
21+
return [
22+
'type' => 'object',
23+
'properties' => [
24+
// `new \stdClass()` so `json_encode` emits `{}` (an empty JSON Schema =
25+
// "any value"); `[]` would encode as `[]` and fail MCP schema validation.
26+
'result' => new \stdClass(),
27+
'resolvedMethod' => ApiMethodSummaryToolOutputSchema::item(),
28+
],
29+
'required' => ['result', 'resolvedMethod'],
30+
'additionalProperties' => false,
31+
];
32+
}
2533
}

Schemas/Api/ApiMethodSummaryToolOutputSchema.php

Lines changed: 74 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,63 +15,83 @@
1515

1616
final class ApiMethodSummaryToolOutputSchema
1717
{
18-
public const PARAMETER = [
19-
'type' => 'object',
20-
'properties' => [
21-
'name' => ['type' => 'string'],
22-
'type' => ['type' => ['string', 'null']],
23-
'required' => ['type' => 'boolean'],
24-
'allowsNull' => ['type' => 'boolean'],
25-
'hasDefault' => ['type' => 'boolean'],
26-
'defaultValue' => [],
27-
],
28-
'required' => ['name', 'type', 'required', 'allowsNull', 'hasDefault', 'defaultValue'],
29-
'additionalProperties' => false,
30-
];
31-
32-
public const ITEM = [
33-
'type' => 'object',
34-
'properties' => [
35-
'module' => ['type' => 'string'],
36-
'action' => ['type' => 'string'],
37-
'method' => ['type' => 'string'],
38-
'parameters' => [
39-
'type' => 'array',
40-
'items' => self::PARAMETER,
18+
/**
19+
* @return array<string, mixed>
20+
*/
21+
public static function parameter(): array
22+
{
23+
return [
24+
'type' => 'object',
25+
'properties' => [
26+
'name' => ['type' => 'string'],
27+
'type' => ['type' => ['string', 'null']],
28+
'required' => ['type' => 'boolean'],
29+
'allowsNull' => ['type' => 'boolean'],
30+
'hasDefault' => ['type' => 'boolean'],
31+
// `new \stdClass()` so `json_encode` emits `{}` (an empty JSON Schema =
32+
// "any value"); `[]` would encode as `[]` and fail MCP schema validation.
33+
'defaultValue' => new \stdClass(),
4134
],
42-
'operationCategory' => [
43-
'type' => ['string', 'null'],
44-
'enum' => [
45-
ApiMethodOperationClassifier::CATEGORY_READ,
46-
ApiMethodOperationClassifier::CATEGORY_CREATE,
47-
ApiMethodOperationClassifier::CATEGORY_UPDATE,
48-
ApiMethodOperationClassifier::CATEGORY_DELETE,
49-
null,
35+
'required' => ['name', 'type', 'required', 'allowsNull', 'hasDefault', 'defaultValue'],
36+
'additionalProperties' => false,
37+
];
38+
}
39+
40+
/**
41+
* @return array<string, mixed>
42+
*/
43+
public static function item(): array
44+
{
45+
return [
46+
'type' => 'object',
47+
'properties' => [
48+
'module' => ['type' => 'string'],
49+
'action' => ['type' => 'string'],
50+
'method' => ['type' => 'string'],
51+
'parameters' => [
52+
'type' => 'array',
53+
'items' => self::parameter(),
54+
],
55+
'operationCategory' => [
56+
'type' => ['string', 'null'],
57+
'enum' => [
58+
ApiMethodOperationClassifier::CATEGORY_READ,
59+
ApiMethodOperationClassifier::CATEGORY_CREATE,
60+
ApiMethodOperationClassifier::CATEGORY_UPDATE,
61+
ApiMethodOperationClassifier::CATEGORY_DELETE,
62+
null,
63+
],
5064
],
5165
],
52-
],
53-
'required' => [
54-
'module',
55-
'action',
56-
'method',
57-
'parameters',
58-
'operationCategory',
59-
],
60-
'additionalProperties' => false,
61-
];
66+
'required' => [
67+
'module',
68+
'action',
69+
'method',
70+
'parameters',
71+
'operationCategory',
72+
],
73+
'additionalProperties' => false,
74+
];
75+
}
6276

63-
public const PAGINATED_LIST = [
64-
'type' => 'object',
65-
'properties' => [
66-
'methods' => [
67-
'type' => 'array',
68-
'items' => self::ITEM,
77+
/**
78+
* @return array<string, mixed>
79+
*/
80+
public static function paginatedList(): array
81+
{
82+
return [
83+
'type' => 'object',
84+
'properties' => [
85+
'methods' => [
86+
'type' => 'array',
87+
'items' => self::item(),
88+
],
89+
'next_cursor' => ['type' => ['string', 'null']],
90+
'has_more' => ['type' => 'boolean'],
91+
'total_rows' => ['type' => 'integer'],
6992
],
70-
'next_cursor' => ['type' => ['string', 'null']],
71-
'has_more' => ['type' => 'boolean'],
72-
'total_rows' => ['type' => 'integer'],
73-
],
74-
'required' => ['methods', 'next_cursor', 'has_more', 'total_rows'],
75-
'additionalProperties' => false,
76-
];
93+
'required' => ['methods', 'next_cursor', 'has_more', 'total_rows'],
94+
'additionalProperties' => false,
95+
];
96+
}
7797
}

0 commit comments

Comments
 (0)