mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-07-29 04:31:50 +00:00
Added MCP tools to list footprints, storelocations, etc. via MCP
This commit is contained in:
parent
3056809afd
commit
f14418d889
14 changed files with 770 additions and 0 deletions
|
|
@ -33,6 +33,8 @@ use ApiPlatform\Metadata\Delete;
|
|||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Link;
|
||||
use ApiPlatform\Metadata\McpTool;
|
||||
use ApiPlatform\Metadata\McpToolCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
|
|
@ -40,7 +42,12 @@ use ApiPlatform\Serializer\Filter\PropertyFilter;
|
|||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\EDA\EDACategoryInfo;
|
||||
use App\Mcp\DTO\ElementByIdInput;
|
||||
use App\Mcp\DTO\StructuralElementOverview;
|
||||
use App\Mcp\DTO\StructuralElementSearchInput;
|
||||
use App\Repository\Parts\CategoryRepository;
|
||||
use App\State\Mcp\GetStructuralElementDetailsProcessor;
|
||||
use App\State\Mcp\ListStructuralElementsProcessor;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use App\Entity\Attachments\CategoryAttachment;
|
||||
|
|
@ -77,6 +84,28 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
],
|
||||
normalizationContext: ['groups' => ['category:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['category:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
mcp: [
|
||||
'list_categories' => new McpToolCollection(
|
||||
title: 'List/search categories',
|
||||
description: 'List all part categories, optionally filtered by a keyword matched against the name and comment. Categories are used to group parts by their function.',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
output: StructuralElementOverview::class,
|
||||
normalizationContext: ['groups' => ['mcp_structural_overview:read']],
|
||||
input: StructuralElementSearchInput::class,
|
||||
security: 'is_granted("@categories.read")',
|
||||
processor: ListStructuralElementsProcessor::class,
|
||||
),
|
||||
'get_category_details' => new McpTool(
|
||||
title: 'Get category details by ID',
|
||||
description: 'Get detailed information about a specific part category by its database ID.',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
normalizationContext: ['groups' => ['category:read', 'api:basic:read']],
|
||||
input: ElementByIdInput::class,
|
||||
security: 'is_granted("@categories.read")',
|
||||
validate: true,
|
||||
processor: GetStructuralElementDetailsProcessor::class,
|
||||
),
|
||||
],
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ use ApiPlatform\Metadata\Delete;
|
|||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Link;
|
||||
use ApiPlatform\Metadata\McpTool;
|
||||
use ApiPlatform\Metadata\McpToolCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
|
|
@ -40,7 +42,12 @@ use ApiPlatform\Serializer\Filter\PropertyFilter;
|
|||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\EDA\EDAFootprintInfo;
|
||||
use App\Mcp\DTO\ElementByIdInput;
|
||||
use App\Mcp\DTO\StructuralElementOverview;
|
||||
use App\Mcp\DTO\StructuralElementSearchInput;
|
||||
use App\Repository\Parts\FootprintRepository;
|
||||
use App\State\Mcp\GetStructuralElementDetailsProcessor;
|
||||
use App\State\Mcp\ListStructuralElementsProcessor;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use App\Entity\Attachments\FootprintAttachment;
|
||||
|
|
@ -76,6 +83,28 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
],
|
||||
normalizationContext: ['groups' => ['footprint:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['footprint:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
mcp: [
|
||||
'list_footprints' => new McpToolCollection(
|
||||
title: 'List/search footprints',
|
||||
description: 'List all footprints, optionally filtered by a keyword matched against the name and comment. Footprints describe the physical package/shape of a part.',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
output: StructuralElementOverview::class,
|
||||
normalizationContext: ['groups' => ['mcp_structural_overview:read']],
|
||||
input: StructuralElementSearchInput::class,
|
||||
security: 'is_granted("@footprints.read")',
|
||||
processor: ListStructuralElementsProcessor::class,
|
||||
),
|
||||
'get_footprint_details' => new McpTool(
|
||||
title: 'Get footprint details by ID',
|
||||
description: 'Get detailed information about a specific footprint by its database ID.',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
normalizationContext: ['groups' => ['footprint:read', 'api:basic:read']],
|
||||
input: ElementByIdInput::class,
|
||||
security: 'is_granted("@footprints.read")',
|
||||
validate: true,
|
||||
processor: GetStructuralElementDetailsProcessor::class,
|
||||
),
|
||||
],
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
|
||||
|
|
|
|||
|
|
@ -33,13 +33,20 @@ use ApiPlatform\Metadata\Delete;
|
|||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Link;
|
||||
use ApiPlatform\Metadata\McpTool;
|
||||
use ApiPlatform\Metadata\McpToolCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Mcp\DTO\ElementByIdInput;
|
||||
use App\Mcp\DTO\StructuralElementOverview;
|
||||
use App\Mcp\DTO\StructuralElementSearchInput;
|
||||
use App\Repository\Parts\ManufacturerRepository;
|
||||
use App\State\Mcp\GetStructuralElementDetailsProcessor;
|
||||
use App\State\Mcp\ListStructuralElementsProcessor;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use App\Entity\Attachments\ManufacturerAttachment;
|
||||
|
|
@ -75,6 +82,28 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
],
|
||||
normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['manufacturer:write', 'company:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
mcp: [
|
||||
'list_manufacturers' => new McpToolCollection(
|
||||
title: 'List/search manufacturers',
|
||||
description: 'List all manufacturers, optionally filtered by a keyword matched against the name and comment.',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
output: StructuralElementOverview::class,
|
||||
normalizationContext: ['groups' => ['mcp_structural_overview:read']],
|
||||
input: StructuralElementSearchInput::class,
|
||||
security: 'is_granted("@manufacturers.read")',
|
||||
processor: ListStructuralElementsProcessor::class,
|
||||
),
|
||||
'get_manufacturer_details' => new McpTool(
|
||||
title: 'Get manufacturer details by ID',
|
||||
description: 'Get detailed information about a specific manufacturer by its database ID.',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read']],
|
||||
input: ElementByIdInput::class,
|
||||
security: 'is_granted("@manufacturers.read")',
|
||||
validate: true,
|
||||
processor: GetStructuralElementDetailsProcessor::class,
|
||||
),
|
||||
],
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
|
||||
|
|
|
|||
|
|
@ -33,13 +33,20 @@ use ApiPlatform\Metadata\Delete;
|
|||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Link;
|
||||
use ApiPlatform\Metadata\McpTool;
|
||||
use ApiPlatform\Metadata\McpToolCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Mcp\DTO\ElementByIdInput;
|
||||
use App\Mcp\DTO\StructuralElementOverview;
|
||||
use App\Mcp\DTO\StructuralElementSearchInput;
|
||||
use App\Repository\Parts\MeasurementUnitRepository;
|
||||
use App\State\Mcp\GetStructuralElementDetailsProcessor;
|
||||
use App\State\Mcp\ListStructuralElementsProcessor;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
|
@ -80,6 +87,28 @@ use Symfony\Component\Validator\Constraints\Length;
|
|||
],
|
||||
normalizationContext: ['groups' => ['measurement_unit:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['measurement_unit:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
mcp: [
|
||||
'list_measurement_units' => new McpToolCollection(
|
||||
title: 'List/search measurement units',
|
||||
description: 'List all measurement units, optionally filtered by a keyword matched against the name and comment. Measurement units describe how the amount of a part is measured (e.g. pieces, meters, grams).',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
output: StructuralElementOverview::class,
|
||||
normalizationContext: ['groups' => ['mcp_structural_overview:read']],
|
||||
input: StructuralElementSearchInput::class,
|
||||
security: 'is_granted("@measurement_units.read")',
|
||||
processor: ListStructuralElementsProcessor::class,
|
||||
),
|
||||
'get_measurement_unit_details' => new McpTool(
|
||||
title: 'Get measurement unit details by ID',
|
||||
description: 'Get detailed information about a specific measurement unit by its database ID.',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
normalizationContext: ['groups' => ['measurement_unit:read', 'api:basic:read']],
|
||||
input: ElementByIdInput::class,
|
||||
security: 'is_granted("@measurement_units.read")',
|
||||
validate: true,
|
||||
processor: GetStructuralElementDetailsProcessor::class,
|
||||
),
|
||||
],
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment", "unit"])]
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ use ApiPlatform\Metadata\ApiResource;
|
|||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\McpTool;
|
||||
use ApiPlatform\Metadata\McpToolCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
|
|
@ -40,7 +42,12 @@ use App\ApiPlatform\Filter\LikeFilter;
|
|||
use App\Entity\Base\AbstractPartsContainingDBElement;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\Parameters\PartCustomStateParameter;
|
||||
use App\Mcp\DTO\ElementByIdInput;
|
||||
use App\Mcp\DTO\StructuralElementOverview;
|
||||
use App\Mcp\DTO\StructuralElementSearchInput;
|
||||
use App\Repository\Parts\PartCustomStateRepository;
|
||||
use App\State\Mcp\GetStructuralElementDetailsProcessor;
|
||||
use App\State\Mcp\ListStructuralElementsProcessor;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
|
|
@ -67,6 +74,28 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
],
|
||||
normalizationContext: ['groups' => ['part_custom_state:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['part_custom_state:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||
mcp: [
|
||||
'list_part_custom_states' => new McpToolCollection(
|
||||
title: 'List/search part custom states',
|
||||
description: 'List all part custom states, optionally filtered by a keyword matched against the name and comment. Custom states are used to mark parts with a custom status (e.g. "obsolete", "needs review").',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
output: StructuralElementOverview::class,
|
||||
normalizationContext: ['groups' => ['mcp_structural_overview:read']],
|
||||
input: StructuralElementSearchInput::class,
|
||||
security: 'is_granted("@part_custom_states.read")',
|
||||
processor: ListStructuralElementsProcessor::class,
|
||||
),
|
||||
'get_part_custom_state_details' => new McpTool(
|
||||
title: 'Get part custom state details by ID',
|
||||
description: 'Get detailed information about a specific part custom state by its database ID.',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
normalizationContext: ['groups' => ['part_custom_state:read', 'api:basic:read']],
|
||||
input: ElementByIdInput::class,
|
||||
security: 'is_granted("@part_custom_states.read")',
|
||||
validate: true,
|
||||
processor: GetStructuralElementDetailsProcessor::class,
|
||||
),
|
||||
],
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name"])]
|
||||
|
|
|
|||
|
|
@ -33,13 +33,20 @@ use ApiPlatform\Metadata\Delete;
|
|||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Link;
|
||||
use ApiPlatform\Metadata\McpTool;
|
||||
use ApiPlatform\Metadata\McpToolCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Mcp\DTO\ElementByIdInput;
|
||||
use App\Mcp\DTO\StructuralElementOverview;
|
||||
use App\Mcp\DTO\StructuralElementSearchInput;
|
||||
use App\Repository\Parts\StorelocationRepository;
|
||||
use App\State\Mcp\GetStructuralElementDetailsProcessor;
|
||||
use App\State\Mcp\ListStructuralElementsProcessor;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use App\Entity\Attachments\StorageLocationAttachment;
|
||||
|
|
@ -76,6 +83,28 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
],
|
||||
normalizationContext: ['groups' => ['location:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['location:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
mcp: [
|
||||
'list_storage_locations' => new McpToolCollection(
|
||||
title: 'List/search storage locations',
|
||||
description: 'List all storage locations, optionally filtered by a keyword matched against the name and comment. Storage locations describe where parts are physically stored.',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
output: StructuralElementOverview::class,
|
||||
normalizationContext: ['groups' => ['mcp_structural_overview:read']],
|
||||
input: StructuralElementSearchInput::class,
|
||||
security: 'is_granted("@storelocations.read")',
|
||||
processor: ListStructuralElementsProcessor::class,
|
||||
),
|
||||
'get_storage_location_details' => new McpTool(
|
||||
title: 'Get storage location details by ID',
|
||||
description: 'Get detailed information about a specific storage location by its database ID.',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
normalizationContext: ['groups' => ['location:read', 'api:basic:read']],
|
||||
input: ElementByIdInput::class,
|
||||
security: 'is_granted("@storelocations.read")',
|
||||
validate: true,
|
||||
processor: GetStructuralElementDetailsProcessor::class,
|
||||
),
|
||||
],
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
|
||||
|
|
|
|||
|
|
@ -33,13 +33,20 @@ use ApiPlatform\Metadata\Delete;
|
|||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Link;
|
||||
use ApiPlatform\Metadata\McpTool;
|
||||
use ApiPlatform\Metadata\McpToolCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Mcp\DTO\ElementByIdInput;
|
||||
use App\Mcp\DTO\StructuralElementOverview;
|
||||
use App\Mcp\DTO\StructuralElementSearchInput;
|
||||
use App\Repository\Parts\SupplierRepository;
|
||||
use App\State\Mcp\GetStructuralElementDetailsProcessor;
|
||||
use App\State\Mcp\ListStructuralElementsProcessor;
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use App\Entity\Attachments\SupplierAttachment;
|
||||
|
|
@ -80,6 +87,28 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
],
|
||||
normalizationContext: ['groups' => ['supplier:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['supplier:write', 'company:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'],
|
||||
mcp: [
|
||||
'list_suppliers' => new McpToolCollection(
|
||||
title: 'List/search suppliers',
|
||||
description: 'List all suppliers, optionally filtered by a keyword matched against the name and comment.',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
output: StructuralElementOverview::class,
|
||||
normalizationContext: ['groups' => ['mcp_structural_overview:read']],
|
||||
input: StructuralElementSearchInput::class,
|
||||
security: 'is_granted("@suppliers.read")',
|
||||
processor: ListStructuralElementsProcessor::class,
|
||||
),
|
||||
'get_supplier_details' => new McpTool(
|
||||
title: 'Get supplier details by ID',
|
||||
description: 'Get detailed information about a specific supplier by its database ID.',
|
||||
annotations: ['readOnlyHint' => true, 'destructiveHint' => false, 'idempotentHint' => true, 'openWorldHint' => false],
|
||||
normalizationContext: ['groups' => ['supplier:read', 'company:read', 'api:basic:read']],
|
||||
input: ElementByIdInput::class,
|
||||
security: 'is_granted("@suppliers.read")',
|
||||
validate: true,
|
||||
processor: GetStructuralElementDetailsProcessor::class,
|
||||
),
|
||||
],
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
|
||||
|
|
|
|||
47
src/Mcp/DTO/StructuralElementOverview.php
Normal file
47
src/Mcp/DTO/StructuralElementOverview.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\DTO;
|
||||
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
* A lean overview projection of a structural "master data" element (category, footprint, manufacturer, ...),
|
||||
* used by the list_X MCP tools. Use the corresponding get_X_details tool with the id to fetch full details.
|
||||
*
|
||||
* The properties are tagged with the 'mcp_structural_overview:read' group (and the list_X mcp tools set
|
||||
* this as their normalizationContext), because otherwise the operation would silently fall back to the
|
||||
* backing entity's own (unrelated) normalization groups, which don't match these plain properties and
|
||||
* would filter them out entirely.
|
||||
*/
|
||||
readonly class StructuralElementOverview
|
||||
{
|
||||
public function __construct(
|
||||
/** @var int The database ID of the element, to be used with the corresponding get_X_details tool */
|
||||
#[Groups(['mcp_structural_overview:read'])]
|
||||
public int $id,
|
||||
/** @var string The name of the element */
|
||||
#[Groups(['mcp_structural_overview:read'])]
|
||||
public string $name,
|
||||
) {
|
||||
}
|
||||
}
|
||||
36
src/Mcp/DTO/StructuralElementSearchInput.php
Normal file
36
src/Mcp/DTO/StructuralElementSearchInput.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\DTO;
|
||||
|
||||
/**
|
||||
* Generic input for listing/searching one of the structural "master data" entities (categories, footprints,
|
||||
* manufacturers, storage locations, suppliers, measurement units, part custom states).
|
||||
*/
|
||||
readonly class StructuralElementSearchInput
|
||||
{
|
||||
public function __construct(
|
||||
/** @var string|null Optional keyword to filter by name or comment (case-insensitive substring match). If omitted, all elements are returned. */
|
||||
public ?string $keyword = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
68
src/Serializer/StructuralElementOverviewNormalizer.php
Normal file
68
src/Serializer/StructuralElementOverviewNormalizer.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Serializer;
|
||||
|
||||
use App\Mcp\DTO\StructuralElementOverview;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
/**
|
||||
* Normalizes StructuralElementOverview as a plain {id, name} object, without the JSON-LD "@id"/"@type"
|
||||
* metadata that ApiPlatform\JsonLd\Serializer\ItemNormalizer would otherwise add (as a blank/genid node,
|
||||
* since this DTO is intentionally not an API resource). That metadata is useless clutter for the list_X
|
||||
* MCP tools, whose whole point is a lean id+name overview.
|
||||
*
|
||||
* @see \App\Tests\Serializer\StructuralElementOverviewNormalizerTest
|
||||
*/
|
||||
class StructuralElementOverviewNormalizer implements NormalizerInterface
|
||||
{
|
||||
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
|
||||
{
|
||||
return $data instanceof StructuralElementOverview;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{id: int, name: string}
|
||||
*/
|
||||
public function normalize($object, ?string $format = null, array $context = []): array
|
||||
{
|
||||
if (!$object instanceof StructuralElementOverview) {
|
||||
throw new \InvalidArgumentException('This normalizer only supports StructuralElementOverview objects!');
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $object->id,
|
||||
'name' => $object->name,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool[]
|
||||
*/
|
||||
public function getSupportedTypes(?string $format): array
|
||||
{
|
||||
return [
|
||||
StructuralElementOverview::class => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
70
src/State/Mcp/GetStructuralElementDetailsProcessor.php
Normal file
70
src/State/Mcp/GetStructuralElementDetailsProcessor.php
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\State\Mcp;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Mcp\DTO\ElementByIdInput;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
|
||||
/**
|
||||
* Generic get-by-id processor shared by all structural "master data" entities (categories, footprints,
|
||||
* manufacturers, storage locations, suppliers, measurement units, part custom states). The concrete entity
|
||||
* class is determined from the operation, so this single processor can be reused for all of them.
|
||||
*/
|
||||
class GetStructuralElementDetailsProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly AuthorizationCheckerInterface $authorizationChecker,
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): AbstractStructuralDBElement
|
||||
{
|
||||
if (!$data instanceof ElementByIdInput) {
|
||||
throw new \InvalidArgumentException('Expected ElementByIdInput');
|
||||
}
|
||||
|
||||
$class = $operation->getClass();
|
||||
if (!is_a($class, AbstractStructuralDBElement::class, true)) {
|
||||
throw new \LogicException(sprintf('%s can only be used for resources extending %s', self::class, AbstractStructuralDBElement::class));
|
||||
}
|
||||
|
||||
$element = $this->entityManager->find($class, $data->id);
|
||||
|
||||
if (!$element instanceof AbstractStructuralDBElement) {
|
||||
throw new NotFoundHttpException(sprintf('%s with id %d not found', (new \ReflectionClass($class))->getShortName(), $data->id));
|
||||
}
|
||||
|
||||
if (!$this->authorizationChecker->isGranted('read', $element)) {
|
||||
throw new AccessDeniedException(sprintf('Access denied to %s with id %d', (new \ReflectionClass($class))->getShortName(), $data->id));
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
78
src/State/Mcp/ListStructuralElementsProcessor.php
Normal file
78
src/State/Mcp/ListStructuralElementsProcessor.php
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\State\Mcp;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Mcp\DTO\StructuralElementOverview;
|
||||
use App\Mcp\DTO\StructuralElementSearchInput;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
* Generic list/search processor shared by all structural "master data" entities (categories, footprints,
|
||||
* manufacturers, storage locations, suppliers, measurement units, part custom states). The concrete entity
|
||||
* class is determined from the operation, so this single processor can be reused for all of them.
|
||||
* Returns a lean id+name overview only; use the corresponding get_X_details tool for full details.
|
||||
*/
|
||||
class ListStructuralElementsProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StructuralElementOverview[]
|
||||
*/
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): array
|
||||
{
|
||||
if (!$data instanceof StructuralElementSearchInput) {
|
||||
throw new \InvalidArgumentException('Expected StructuralElementSearchInput');
|
||||
}
|
||||
|
||||
$class = $operation->getClass();
|
||||
if (!is_a($class, AbstractStructuralDBElement::class, true)) {
|
||||
throw new \LogicException(sprintf('%s can only be used for resources extending %s', self::class, AbstractStructuralDBElement::class));
|
||||
}
|
||||
|
||||
$qb = $this->entityManager->getRepository($class)->createQueryBuilder('element');
|
||||
$qb->select('element.id AS id', 'element.name AS name');
|
||||
|
||||
if ($data->keyword !== null && $data->keyword !== '') {
|
||||
//Escape % and _ characters in the keyword, like PartSearchFilter does
|
||||
$keyword = str_replace(['%', '_'], ['\%', '\_'], $data->keyword);
|
||||
$qb->andWhere('ILIKE(element.name, :keyword) = TRUE OR ILIKE(element.comment, :keyword) = TRUE')
|
||||
->setParameter('keyword', '%'.$keyword.'%');
|
||||
}
|
||||
|
||||
$qb->orderBy('element.name', 'ASC');
|
||||
|
||||
$rows = $qb->getQuery()->getArrayResult();
|
||||
|
||||
return array_map(
|
||||
static fn (array $row): StructuralElementOverview => new StructuralElementOverview(id: $row['id'], name: $row['name']),
|
||||
$rows
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue