Additional tools

This commit is contained in:
snipe
2026-05-08 11:45:30 +01:00
parent 2542221fc9
commit 9c97a06c7e
67 changed files with 5866 additions and 15 deletions
+157 -2
View File
@@ -10,29 +10,89 @@ use App\Mcp\Tools\CheckinLicenseTool;
use App\Mcp\Tools\CheckoutAccessoryTool;
use App\Mcp\Tools\CheckoutAssetTool;
use App\Mcp\Tools\CheckoutComponentTool;
use App\Mcp\Tools\CheckoutConsumableTool;
use App\Mcp\Tools\CheckoutLicenseTool;
use App\Mcp\Tools\CreateAccessoryTool;
use App\Mcp\Tools\CreateAssetModelTool;
use App\Mcp\Tools\CreateAssetTool;
use App\Mcp\Tools\CreateCategoryTool;
use App\Mcp\Tools\CreateCompanyTool;
use App\Mcp\Tools\CreateComponentTool;
use App\Mcp\Tools\CreateConsumableTool;
use App\Mcp\Tools\CreateDepartmentTool;
use App\Mcp\Tools\CreateDepreciationTool;
use App\Mcp\Tools\CreateGroupTool;
use App\Mcp\Tools\CreateLicenseTool;
use App\Mcp\Tools\CreateLocationTool;
use App\Mcp\Tools\CreateMaintenanceTool;
use App\Mcp\Tools\CreateManufacturerTool;
use App\Mcp\Tools\CreateStatusLabelTool;
use App\Mcp\Tools\CreateSupplierTool;
use App\Mcp\Tools\CreateUserTool;
use App\Mcp\Tools\DeleteAccessoryTool;
use App\Mcp\Tools\DeleteAssetModelTool;
use App\Mcp\Tools\DeleteAssetTool;
use App\Mcp\Tools\DeleteCategoryTool;
use App\Mcp\Tools\DeleteCompanyTool;
use App\Mcp\Tools\DeleteComponentTool;
use App\Mcp\Tools\DeleteConsumableTool;
use App\Mcp\Tools\DeleteDepartmentTool;
use App\Mcp\Tools\DeleteDepreciationTool;
use App\Mcp\Tools\DeleteGroupTool;
use App\Mcp\Tools\DeleteLicenseTool;
use App\Mcp\Tools\DeleteLocationTool;
use App\Mcp\Tools\DeleteManufacturerTool;
use App\Mcp\Tools\DeleteStatusLabelTool;
use App\Mcp\Tools\DeleteSupplierTool;
use App\Mcp\Tools\DeleteUserTool;
use App\Mcp\Tools\GetActivityLogTool;
use App\Mcp\Tools\GetCurrentUserTool;
use App\Mcp\Tools\GetUserAssetsTool;
use App\Mcp\Tools\ListAssetModelsTool;
use App\Mcp\Tools\ListAssetsTool;
use App\Mcp\Tools\ListCategoriesTool;
use App\Mcp\Tools\ListCompaniesTool;
use App\Mcp\Tools\ListConsumablesTool;
use App\Mcp\Tools\ListDepreciationsTool;
use App\Mcp\Tools\ListGroupsTool;
use App\Mcp\Tools\ListLicensesTool;
use App\Mcp\Tools\ListLocationsTool;
use App\Mcp\Tools\ListMaintenancesTool;
use App\Mcp\Tools\ListManufacturersTool;
use App\Mcp\Tools\ListStatusLabelsTool;
use App\Mcp\Tools\ListSuppliersTool;
use App\Mcp\Tools\ListUsersTool;
use App\Mcp\Tools\Reset2FATool;
use App\Mcp\Tools\RestoreAssetTool;
use App\Mcp\Tools\RestoreUserTool;
use App\Mcp\Tools\ShowAssetModelTool;
use App\Mcp\Tools\ShowAssetTool;
use App\Mcp\Tools\ShowCategoryTool;
use App\Mcp\Tools\ShowCompanyTool;
use App\Mcp\Tools\ShowConsumableTool;
use App\Mcp\Tools\ShowDepreciationTool;
use App\Mcp\Tools\ShowGroupTool;
use App\Mcp\Tools\ShowLicenseTool;
use App\Mcp\Tools\ShowLocationTool;
use App\Mcp\Tools\ShowManufacturerTool;
use App\Mcp\Tools\ShowStatusLabelTool;
use App\Mcp\Tools\ShowSupplierTool;
use App\Mcp\Tools\ShowUserTool;
use App\Mcp\Tools\UpdateAccessoryTool;
use App\Mcp\Tools\UpdateAssetModelTool;
use App\Mcp\Tools\UpdateAssetTool;
use App\Mcp\Tools\UpdateCategoryTool;
use App\Mcp\Tools\UpdateCompanyTool;
use App\Mcp\Tools\UpdateComponentTool;
use App\Mcp\Tools\UpdateConsumableTool;
use App\Mcp\Tools\UpdateDepartmentTool;
use App\Mcp\Tools\UpdateDepreciationTool;
use App\Mcp\Tools\UpdateGroupTool;
use App\Mcp\Tools\UpdateLicenseTool;
use App\Mcp\Tools\UpdateLocationTool;
use App\Mcp\Tools\UpdateManufacturerTool;
use App\Mcp\Tools\UpdateStatusLabelTool;
use App\Mcp\Tools\UpdateSupplierTool;
use App\Mcp\Tools\UpdateUserTool;
use Laravel\Mcp\Server;
use Laravel\Mcp\Server\Attributes\Instructions;
@@ -45,28 +105,51 @@ use Laravel\Mcp\Server\Attributes\Version;
class SnipeMCPServer extends Server
{
protected array $tools = [
// Assets
ShowAssetTool::class,
ListAssetsTool::class,
CheckoutAssetTool::class,
CheckinAssetTool::class,
CreateAssetTool::class,
UpdateAssetTool::class,
DeleteAssetTool::class,
RestoreAssetTool::class,
CheckoutAssetTool::class,
CheckinAssetTool::class,
AuditAssetTool::class,
// Users
ListUsersTool::class,
ShowUserTool::class,
CreateUserTool::class,
UpdateUserTool::class,
DeleteUserTool::class,
RestoreUserTool::class,
GetCurrentUserTool::class,
GetUserAssetsTool::class,
Reset2FATool::class,
// Accessories
CreateAccessoryTool::class,
UpdateAccessoryTool::class,
DeleteAccessoryTool::class,
CheckoutAccessoryTool::class,
CheckinAccessoryTool::class,
// Components
CreateComponentTool::class,
UpdateComponentTool::class,
DeleteComponentTool::class,
CheckoutComponentTool::class,
CheckinComponentTool::class,
// Consumables
ListConsumablesTool::class,
ShowConsumableTool::class,
CreateConsumableTool::class,
UpdateConsumableTool::class,
DeleteConsumableTool::class,
CheckoutConsumableTool::class,
// Licenses
ListLicensesTool::class,
ShowLicenseTool::class,
CreateLicenseTool::class,
@@ -74,9 +157,81 @@ class SnipeMCPServer extends Server
DeleteLicenseTool::class,
CheckoutLicenseTool::class,
CheckinLicenseTool::class,
// Departments
CreateDepartmentTool::class,
UpdateDepartmentTool::class,
DeleteDepartmentTool::class,
// Companies
ListCompaniesTool::class,
ShowCompanyTool::class,
CreateCompanyTool::class,
UpdateCompanyTool::class,
DeleteCompanyTool::class,
// Categories
ListCategoriesTool::class,
ShowCategoryTool::class,
CreateCategoryTool::class,
UpdateCategoryTool::class,
DeleteCategoryTool::class,
// Manufacturers
ListManufacturersTool::class,
ShowManufacturerTool::class,
CreateManufacturerTool::class,
UpdateManufacturerTool::class,
DeleteManufacturerTool::class,
// Suppliers
ListSuppliersTool::class,
ShowSupplierTool::class,
CreateSupplierTool::class,
UpdateSupplierTool::class,
DeleteSupplierTool::class,
// Status Labels
ListStatusLabelsTool::class,
ShowStatusLabelTool::class,
CreateStatusLabelTool::class,
UpdateStatusLabelTool::class,
DeleteStatusLabelTool::class,
// Locations
ListLocationsTool::class,
ShowLocationTool::class,
CreateLocationTool::class,
UpdateLocationTool::class,
DeleteLocationTool::class,
// Asset Models
ListAssetModelsTool::class,
ShowAssetModelTool::class,
CreateAssetModelTool::class,
UpdateAssetModelTool::class,
DeleteAssetModelTool::class,
// Depreciations
ListDepreciationsTool::class,
ShowDepreciationTool::class,
CreateDepreciationTool::class,
UpdateDepreciationTool::class,
DeleteDepreciationTool::class,
// Groups
ListGroupsTool::class,
ShowGroupTool::class,
CreateGroupTool::class,
UpdateGroupTool::class,
DeleteGroupTool::class,
// Maintenance
ListMaintenancesTool::class,
CreateMaintenanceTool::class,
// Activity Log
GetActivityLogTool::class,
];
protected array $resources = [
+113
View File
@@ -0,0 +1,113 @@
<?php
namespace App\Mcp\Tools;
use App\Events\CheckoutableCheckedOut;
use App\Models\Consumable;
use App\Models\User;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('checkout_consumable')]
#[Title('Checkout Consumable')]
#[Description('Check out a Snipe-IT consumable to a user')]
class CheckoutConsumableTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
'assigned_to' => 'required|integer',
'note' => 'nullable|string|max:65535',
]);
$consumable = $this->resolveConsumable($request);
if (! $consumable) {
return Response::make(Response::error('Consumable not found'));
}
if (! Gate::allows('checkout', $consumable)) {
return Response::make(Response::error('Unauthorized'));
}
if ($consumable->numRemaining() <= 0) {
return Response::make(Response::error('No units remaining'));
}
$user = User::find($request->get('assigned_to'));
if (! $user) {
return Response::make(Response::error('User not found'));
}
$consumable->users()->attach($consumable->id, [
'consumable_id' => $consumable->id,
'created_by' => auth()->id(),
'assigned_to' => $user->id,
'note' => $request->get('note'),
]);
event(new CheckoutableCheckedOut(
$consumable,
$user,
auth()->user(),
$request->get('note'),
[],
1,
));
return Response::make(
Response::text('Consumable '.$consumable->name.' checked out to '.$user->username)
)->withStructuredContent([
'success' => true,
'message' => 'Consumable checked out successfully',
'consumable_id' => $consumable->id,
'consumable_name' => $consumable->name,
'assigned_to_id' => $user->id,
'assigned_to_username' => $user->username,
]);
}
private function resolveConsumable(Request $request): ?Consumable
{
if ($request->filled('id')) {
return Consumable::find($request->get('id'));
}
if ($request->filled('name')) {
return Consumable::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the consumable to check out'),
'name' => $schema->string()->description('Name of the consumable to check out'),
'assigned_to' => $schema->number()->description('User ID to check out to (required)'),
'note' => $schema->string()->description('Optional checkout note'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the checkout succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'consumable_id' => $schema->number()->description('Numeric ID of the consumable'),
'consumable_name' => $schema->string()->description('Name of the consumable'),
'assigned_to_id' => $schema->number()->description('ID of the user the consumable was checked out to'),
'assigned_to_username' => $schema->string()->description('Username of the user the consumable was checked out to'),
];
}
}
+97
View File
@@ -0,0 +1,97 @@
<?php
namespace App\Mcp\Tools;
use App\Models\AssetModel;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('create_asset_model')]
#[Title('Create Asset Model')]
#[Description('Create a new Snipe-IT asset model')]
class CreateAssetModelTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('create', AssetModel::class)) {
return Response::make(Response::error('Unauthorized'));
}
try {
$request->validate([
'name' => 'required|string|max:255',
'category_id' => 'required|integer|exists:categories,id',
'model_number' => 'nullable|string|max:255',
'manufacturer_id' => 'nullable|integer|exists:manufacturers,id',
'depreciation_id' => 'nullable|integer|exists:depreciations,id',
'eol' => 'nullable|integer|min:0|max:240',
'min_amt' => 'nullable|integer|min:0',
'notes' => 'nullable|string',
'requestable' => 'nullable|boolean',
'require_serial' => 'nullable|boolean',
]);
} catch (ValidationException $e) {
return Response::make(Response::error($e->validator->errors()->first()));
}
$assetModel = new AssetModel;
$assetModel->name = $request->get('name');
$assetModel->category_id = $request->get('category_id');
$assetModel->created_by = auth()->id();
foreach (['model_number', 'manufacturer_id', 'depreciation_id', 'eol', 'min_amt', 'notes', 'requestable', 'require_serial'] as $f) {
if ($request->filled($f)) {
$assetModel->{$f} = $request->get($f);
}
}
if ($assetModel->save()) {
return Response::make(
Response::text('Asset model '.$assetModel->name.' created successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Asset model created successfully',
'id' => $assetModel->id,
'name' => $assetModel->name,
'category_id' => $assetModel->category_id,
]);
}
return Response::make(Response::error('Create failed: '.$assetModel->getErrors()->first()));
}
public function schema(JsonSchema $schema): array
{
return [
'name' => $schema->string()->description('Asset model name (required)'),
'category_id' => $schema->number()->description('Category ID (required)'),
'model_number' => $schema->string()->description('Model number'),
'manufacturer_id' => $schema->number()->description('Manufacturer ID'),
'depreciation_id' => $schema->number()->description('Depreciation schedule ID'),
'eol' => $schema->number()->description('End of life in months (0-240)'),
'min_amt' => $schema->number()->description('Minimum quantity alert threshold'),
'notes' => $schema->string()->description('Notes'),
'requestable' => $schema->boolean()->description('Whether the model can be requested'),
'require_serial' => $schema->boolean()->description('Whether serial numbers are required'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the asset model was created'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the new asset model'),
'name' => $schema->string()->description('Name of the new asset model'),
'category_id' => $schema->number()->description('Category ID of the new asset model'),
];
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Asset;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('create_asset')]
#[Title('Create Asset')]
#[Description('Create a new Snipe-IT asset')]
class CreateAssetTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('create', Asset::class)) {
return Response::make(Response::error('Unauthorized'));
}
try {
$request->validate([
'model_id' => 'required|integer|exists:models,id',
'status_id' => 'required|integer|exists:status_labels,id',
'asset_tag' => 'required|string|max:255',
'name' => 'nullable|string|max:255',
'serial' => 'nullable|string',
'company_id' => 'nullable|integer',
'location_id' => 'nullable|integer|exists:locations,id',
'rtd_location_id' => 'nullable|integer|exists:locations,id',
'supplier_id' => 'nullable|integer|exists:suppliers,id',
'purchase_date' => 'nullable|date_format:Y-m-d',
'purchase_cost' => 'nullable|numeric',
'order_number' => 'nullable|string|max:191',
'warranty_months' => 'nullable|integer|min:0|max:240',
'requestable' => 'nullable|boolean',
'notes' => 'nullable|string|max:65535',
]);
} catch (ValidationException $e) {
return Response::make(Response::error($e->validator->errors()->first()));
}
$asset = new Asset;
$asset->model_id = $request->get('model_id');
$asset->status_id = $request->get('status_id');
$asset->asset_tag = $request->get('asset_tag');
$asset->created_by = auth()->id();
foreach (['name', 'serial', 'company_id', 'location_id', 'rtd_location_id', 'supplier_id', 'purchase_date', 'purchase_cost', 'order_number', 'warranty_months', 'requestable', 'notes'] as $field) {
if ($request->filled($field)) {
$asset->{$field} = $request->get($field);
}
}
if ($asset->save()) {
return Response::make(
Response::text('Asset '.$asset->asset_tag.' created successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Asset created successfully',
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
'name' => $asset->name,
]);
}
return Response::make(Response::error('Create failed: '.$asset->getErrors()->first()));
}
public function schema(JsonSchema $schema): array
{
return [
'model_id' => $schema->number()->description('Asset model ID (required)'),
'status_id' => $schema->number()->description('Status label ID (required)'),
'asset_tag' => $schema->string()->description('Asset tag (required)'),
'name' => $schema->string()->description('Display name for the asset'),
'serial' => $schema->string()->description('Serial number'),
'company_id' => $schema->number()->description('Company ID'),
'location_id' => $schema->number()->description('Current location ID'),
'rtd_location_id' => $schema->number()->description('Default RTD location ID'),
'supplier_id' => $schema->number()->description('Supplier ID'),
'purchase_date' => $schema->string()->description('Purchase date (YYYY-MM-DD)'),
'purchase_cost' => $schema->number()->description('Purchase cost'),
'order_number' => $schema->string()->description('Order number'),
'warranty_months' => $schema->number()->description('Warranty length in months (0-240)'),
'requestable' => $schema->boolean()->description('Whether the asset is user-requestable'),
'notes' => $schema->string()->description('Notes'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the asset was created'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the new asset'),
'asset_tag' => $schema->string()->description('Asset tag of the new asset'),
'name' => $schema->string()->description('Display name of the new asset'),
];
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Category;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('create_category')]
#[Title('Create Category')]
#[Description('Create a new Snipe-IT category')]
class CreateCategoryTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('create', Category::class)) {
return Response::make(Response::error('Unauthorized'));
}
try {
$request->validate([
'name' => 'required|string|max:255',
'category_type' => 'required|string|in:asset,accessory,consumable,component,license',
'checkin_email' => 'nullable|boolean',
'require_acceptance' => 'nullable|boolean',
'use_default_eula' => 'nullable|boolean',
'notes' => 'nullable|string',
]);
} catch (ValidationException $e) {
return Response::make(Response::error($e->validator->errors()->first()));
}
$category = new Category;
$category->name = $request->get('name');
$category->category_type = $request->get('category_type');
$category->created_by = auth()->id();
foreach (['checkin_email', 'require_acceptance', 'use_default_eula', 'notes'] as $field) {
if ($request->filled($field)) {
$category->{$field} = $request->get($field);
}
}
if ($category->save()) {
return Response::make(
Response::text('Category '.$category->name.' created successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Category created successfully',
'id' => $category->id,
'name' => $category->name,
'category_type' => $category->category_type,
]);
}
return Response::make(Response::error('Create failed: '.$category->getErrors()->first()));
}
public function schema(JsonSchema $schema): array
{
return [
'name' => $schema->string()->description('Category name (required)'),
'category_type' => $schema->string()->description('Category type (required): asset, accessory, consumable, component, or license'),
'checkin_email' => $schema->boolean()->description('Send checkin email when items are checked in'),
'require_acceptance' => $schema->boolean()->description('Require user acceptance when checking out'),
'use_default_eula' => $schema->boolean()->description('Use the default EULA'),
'notes' => $schema->string()->description('Notes'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the category was created'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the new category'),
'name' => $schema->string()->description('Name of the new category'),
'category_type' => $schema->string()->description('Type of the new category'),
];
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Company;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('create_company')]
#[Title('Create Company')]
#[Description('Create a new Snipe-IT company')]
class CreateCompanyTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('create', Company::class)) {
return Response::make(Response::error('Unauthorized'));
}
try {
$request->validate([
'name' => 'required|string|max:255',
'phone' => 'nullable|string',
'fax' => 'nullable|string',
'email' => 'nullable|string',
'notes' => 'nullable|string',
]);
} catch (ValidationException $e) {
return Response::make(Response::error($e->validator->errors()->first()));
}
$company = new Company;
$company->name = $request->get('name');
if ($request->filled('phone')) {
$company->phone = $request->get('phone');
}
if ($request->filled('fax')) {
$company->fax = $request->get('fax');
}
if ($request->filled('email')) {
$company->email = $request->get('email');
}
if ($request->filled('notes')) {
$company->notes = $request->get('notes');
}
$company->created_by = auth()->id();
if ($company->save()) {
return Response::make(
Response::text('Company '.$company->name.' created successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Company created successfully',
'id' => $company->id,
'name' => $company->name,
]);
}
return Response::make(Response::error('Create failed: '.$company->getErrors()->first()));
}
public function schema(JsonSchema $schema): array
{
return [
'name' => $schema->string()->description('Company name (required)'),
'phone' => $schema->string()->description('Company phone number'),
'fax' => $schema->string()->description('Company fax number'),
'email' => $schema->string()->description('Company email address'),
'notes' => $schema->string()->description('Notes'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the company was created'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the new company'),
'name' => $schema->string()->description('Name of the new company'),
];
}
}
+106
View File
@@ -0,0 +1,106 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Consumable;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('create_consumable')]
#[Title('Create Consumable')]
#[Description('Create a new Snipe-IT consumable')]
class CreateConsumableTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('create', Consumable::class)) {
return Response::make(Response::error('Unauthorized'));
}
try {
$request->validate([
'name' => 'required|string|max:255',
'qty' => 'required|integer|min:0',
'category_id' => 'required|integer|exists:categories,id',
'company_id' => 'nullable|integer',
'location_id' => 'nullable|integer|exists:locations,id',
'manufacturer_id' => 'nullable|integer|exists:manufacturers,id',
'supplier_id' => 'nullable|integer|exists:suppliers,id',
'item_no' => 'nullable|string|max:255',
'order_number' => 'nullable|string|max:255',
'model_number' => 'nullable|string|max:255',
'purchase_cost' => 'nullable|numeric|min:0',
'purchase_date' => 'nullable|date_format:Y-m-d',
'min_amt' => 'nullable|integer|min:0',
'requestable' => 'nullable|boolean',
'notes' => 'nullable|string',
]);
} catch (ValidationException $e) {
return Response::make(Response::error($e->validator->errors()->first()));
}
$consumable = new Consumable;
$consumable->fill($request->only([
'name', 'qty', 'category_id', 'company_id', 'location_id', 'manufacturer_id',
'supplier_id', 'item_no', 'order_number', 'model_number', 'purchase_cost',
'purchase_date', 'min_amt', 'requestable', 'notes',
]));
$consumable->created_by = auth()->id();
if ($consumable->save()) {
return Response::make(
Response::text('Consumable '.$consumable->name.' created successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Consumable created successfully',
'id' => $consumable->id,
'name' => $consumable->name,
'qty' => $consumable->qty,
'category_id' => $consumable->category_id,
]);
}
return Response::make(Response::error('Create failed: '.$consumable->getErrors()->first()));
}
public function schema(JsonSchema $schema): array
{
return [
'name' => $schema->string()->description('Consumable name (required)'),
'qty' => $schema->number()->description('Total quantity in stock (required)'),
'category_id' => $schema->number()->description('Category ID — must be a consumable category (required)'),
'company_id' => $schema->number()->description('Company ID'),
'location_id' => $schema->number()->description('Location ID'),
'manufacturer_id' => $schema->number()->description('Manufacturer ID'),
'supplier_id' => $schema->number()->description('Supplier ID'),
'item_no' => $schema->string()->description('Item number'),
'order_number' => $schema->string()->description('Order number'),
'model_number' => $schema->string()->description('Model number'),
'purchase_cost' => $schema->number()->description('Purchase cost per unit'),
'purchase_date' => $schema->string()->description('Purchase date (YYYY-MM-DD)'),
'min_amt' => $schema->number()->description('Minimum quantity threshold for alerts'),
'requestable' => $schema->boolean()->description('Whether users can request this consumable'),
'notes' => $schema->string()->description('Notes'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the consumable was created'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the new consumable'),
'name' => $schema->string()->description('Name of the new consumable'),
'qty' => $schema->number()->description('Total quantity'),
'category_id' => $schema->number()->description('Category ID'),
];
}
}
+74
View File
@@ -0,0 +1,74 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Depreciation;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('create_depreciation')]
#[Title('Create Depreciation')]
#[Description('Create a new Snipe-IT depreciation schedule')]
class CreateDepreciationTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('create', Depreciation::class)) {
return Response::make(Response::error('Unauthorized'));
}
try {
$request->validate([
'name' => 'required|string|max:255',
'months' => 'required|integer|min:1|max:3600',
]);
} catch (ValidationException $e) {
return Response::make(Response::error($e->validator->errors()->first()));
}
$depreciation = new Depreciation;
$depreciation->name = $request->get('name');
$depreciation->months = $request->get('months');
if ($depreciation->save()) {
return Response::make(
Response::text('Depreciation '.$depreciation->name.' created successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Depreciation created successfully',
'id' => $depreciation->id,
'name' => $depreciation->name,
'months' => $depreciation->months,
]);
}
return Response::make(Response::error('Create failed: '.$depreciation->getErrors()->first()));
}
public function schema(JsonSchema $schema): array
{
return [
'name' => $schema->string()->description('Depreciation name (required)'),
'months' => $schema->number()->description('Depreciation period in months (required, 1-3600)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the depreciation was created'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the new depreciation'),
'name' => $schema->string()->description('Name of the new depreciation'),
'months' => $schema->number()->description('Depreciation period in months'),
];
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Group;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('create_group')]
#[Title('Create Group')]
#[Description('Create a new Snipe-IT permission group')]
class CreateGroupTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('superadmin')) {
return Response::make(Response::error('Unauthorized'));
}
try {
$request->validate([
'name' => 'required|string|max:255',
'notes' => 'nullable|string',
]);
} catch (ValidationException $e) {
return Response::make(Response::error($e->validator->errors()->first()));
}
$group = new Group;
$group->name = $request->get('name');
if ($request->filled('notes')) {
$group->notes = $request->get('notes');
}
$group->created_by = auth()->id();
if ($group->save()) {
return Response::make(
Response::text('Group '.$group->name.' created successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Group created successfully',
'id' => $group->id,
'name' => $group->name,
]);
}
return Response::make(Response::error('Create failed: '.$group->getErrors()->first()));
}
public function schema(JsonSchema $schema): array
{
return [
'name' => $schema->string()->description('Group name (required, must be unique)'),
'notes' => $schema->string()->description('Notes about the group'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the group was created'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the new group'),
'name' => $schema->string()->description('Name of the new group'),
];
}
}
+97
View File
@@ -0,0 +1,97 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Location;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('create_location')]
#[Title('Create Location')]
#[Description('Create a new Snipe-IT location')]
class CreateLocationTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('create', Location::class)) {
return Response::make(Response::error('Unauthorized'));
}
try {
$request->validate([
'name' => 'required|string|max:255',
'address' => 'nullable|string',
'address2' => 'nullable|string',
'city' => 'nullable|string',
'state' => 'nullable|string',
'country' => 'nullable|string',
'zip' => 'nullable|string',
'phone' => 'nullable|string|max:255',
'fax' => 'nullable|string|max:255',
'currency' => 'nullable|string',
'parent_id' => 'nullable|integer|exists:locations,id',
'manager_id' => 'nullable|integer|exists:users,id',
]);
} catch (ValidationException $e) {
return Response::make(Response::error($e->validator->errors()->first()));
}
$location = new Location;
$location->name = $request->get('name');
foreach (['address', 'address2', 'city', 'state', 'country', 'zip', 'phone', 'fax', 'currency', 'parent_id', 'manager_id'] as $field) {
if ($request->filled($field)) {
$location->{$field} = $request->get($field);
}
}
if ($location->save()) {
return Response::make(
Response::text('Location '.$location->name.' created successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Location created successfully',
'id' => $location->id,
'name' => $location->name,
]);
}
return Response::make(Response::error('Create failed: '.$location->getErrors()->first()));
}
public function schema(JsonSchema $schema): array
{
return [
'name' => $schema->string()->description('Location name (required)'),
'address' => $schema->string()->description('Street address'),
'address2' => $schema->string()->description('Address line 2'),
'city' => $schema->string()->description('City'),
'state' => $schema->string()->description('State'),
'country' => $schema->string()->description('Country'),
'zip' => $schema->string()->description('Zip code'),
'phone' => $schema->string()->description('Phone number'),
'fax' => $schema->string()->description('Fax number'),
'currency' => $schema->string()->description('Currency code'),
'parent_id' => $schema->number()->description('Parent location ID'),
'manager_id' => $schema->number()->description('Manager user ID'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the location was created'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the new location'),
'name' => $schema->string()->description('Name of the new location'),
];
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Asset;
use App\Models\Maintenance;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('create_maintenance')]
#[Title('Create Maintenance')]
#[Description('Create a new asset maintenance record')]
class CreateMaintenanceTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('update', Asset::class)) {
return Response::make(Response::error('Unauthorized'));
}
try {
$request->validate([
'asset_id' => 'required|integer|exists:assets,id',
'title' => 'required|string|max:255',
'asset_maintenance_type' => 'nullable|string|max:255',
'supplier_id' => 'nullable|integer|exists:suppliers,id',
'is_warranty' => 'nullable|boolean',
'cost' => 'nullable|numeric|min:0',
'start_date' => 'nullable|date_format:Y-m-d',
'completion_date' => 'nullable|date_format:Y-m-d',
'notes' => 'nullable|string',
'user_id' => 'nullable|integer|exists:users,id',
]);
} catch (ValidationException $e) {
return Response::make(Response::error($e->validator->errors()->first()));
}
$maintenance = new Maintenance;
$maintenance->asset_id = $request->get('asset_id');
$maintenance->name = $request->get('title');
$maintenance->asset_maintenance_type = $request->get('asset_maintenance_type', 'Maintenance');
$maintenance->start_date = $request->filled('start_date') ? $request->get('start_date') : now()->format('Y-m-d');
$maintenance->created_by = auth()->id();
$maintenance->is_warranty = 0;
foreach (['supplier_id', 'is_warranty', 'cost', 'completion_date', 'notes', 'user_id'] as $field) {
if ($request->filled($field)) {
$maintenance->{$field} = $request->get($field);
}
}
if ($maintenance->save()) {
$maintenance->load('asset');
return Response::make(
Response::text('Maintenance '.$maintenance->name.' created successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Maintenance created successfully',
'id' => $maintenance->id,
'title' => $maintenance->name,
'asset_id' => $maintenance->asset_id,
'asset_tag' => $maintenance->asset?->asset_tag,
]);
}
return Response::make(Response::error('Create failed: '.$maintenance->getErrors()->first()));
}
public function schema(JsonSchema $schema): array
{
return [
'asset_id' => $schema->number()->description('Asset ID the maintenance is for (required)'),
'title' => $schema->string()->description('Maintenance title/name (required)'),
'asset_maintenance_type' => $schema->string()->description('Type of maintenance (e.g. maintenance, repair, upgrade)'),
'supplier_id' => $schema->number()->description('Supplier ID'),
'is_warranty' => $schema->boolean()->description('Whether this is a warranty maintenance'),
'cost' => $schema->number()->description('Cost of the maintenance'),
'start_date' => $schema->string()->description('Start date (YYYY-MM-DD, defaults to today)'),
'completion_date' => $schema->string()->description('Completion date (YYYY-MM-DD)'),
'notes' => $schema->string()->description('Notes about the maintenance'),
'user_id' => $schema->number()->description('Technician user ID'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the maintenance was created'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the new maintenance record'),
'title' => $schema->string()->description('Title of the maintenance'),
'asset_id' => $schema->number()->description('Asset ID'),
'asset_tag' => $schema->string()->description('Asset tag'),
];
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Manufacturer;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('create_manufacturer')]
#[Title('Create Manufacturer')]
#[Description('Create a new Snipe-IT manufacturer')]
class CreateManufacturerTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('create', Manufacturer::class)) {
return Response::make(Response::error('Unauthorized'));
}
try {
$request->validate([
'name' => 'required|string|max:255',
'url' => 'nullable|string|max:255',
'support_url' => 'nullable|string|max:255',
'support_email' => 'nullable|email|max:191',
'support_phone' => 'nullable|string|max:191',
'warranty_lookup_url' => 'nullable|string|max:255',
'notes' => 'nullable|string',
]);
} catch (ValidationException $e) {
return Response::make(Response::error($e->validator->errors()->first()));
}
$manufacturer = new Manufacturer;
$manufacturer->fill($request->only([
'name', 'url', 'support_url', 'support_email', 'support_phone', 'warranty_lookup_url', 'notes',
]));
if ($manufacturer->save()) {
return Response::make(
Response::text('Manufacturer '.$manufacturer->name.' created successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Manufacturer created successfully',
'id' => $manufacturer->id,
'name' => $manufacturer->name,
]);
}
return Response::make(Response::error('Create failed: '.$manufacturer->getErrors()->first()));
}
public function schema(JsonSchema $schema): array
{
return [
'name' => $schema->string()->description('Manufacturer name (required)'),
'url' => $schema->string()->description('Manufacturer website URL'),
'support_url' => $schema->string()->description('Support website URL'),
'support_email' => $schema->string()->description('Support email address'),
'support_phone' => $schema->string()->description('Support phone number'),
'warranty_lookup_url' => $schema->string()->description('Warranty lookup URL'),
'notes' => $schema->string()->description('Notes'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the manufacturer was created'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the new manufacturer'),
'name' => $schema->string()->description('Name of the new manufacturer'),
];
}
}
+95
View File
@@ -0,0 +1,95 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Statuslabel;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('create_status_label')]
#[Title('Create Status Label')]
#[Description('Create a new Snipe-IT status label')]
class CreateStatusLabelTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('create', Statuslabel::class)) {
return Response::make(Response::error('Unauthorized'));
}
try {
$request->validate([
'name' => 'required|string|max:255',
'type' => 'required|string|in:deployable,pending,archived,undeployable',
'color' => 'nullable|string',
'notes' => 'nullable|string',
'default_label' => 'nullable|boolean',
'show_in_nav' => 'nullable|boolean',
]);
} catch (ValidationException $e) {
return Response::make(Response::error($e->validator->errors()->first()));
}
$statuslabel = new Statuslabel;
$statuslabel->name = $request->get('name');
$statusType = Statuslabel::getStatuslabelTypesForDB($request->get('type'));
$statuslabel->deployable = $statusType['deployable'];
$statuslabel->pending = $statusType['pending'];
$statuslabel->archived = $statusType['archived'];
if ($request->filled('color')) {
$statuslabel->color = $request->get('color');
}
if ($request->filled('notes')) {
$statuslabel->notes = $request->get('notes');
}
$statuslabel->default_label = $request->get('default_label', 0);
$statuslabel->show_in_nav = $request->get('show_in_nav', 0);
if ($statuslabel->save()) {
return Response::make(
Response::text('Status label '.$statuslabel->name.' created successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Status label created successfully',
'id' => $statuslabel->id,
'name' => $statuslabel->name,
'type' => $statuslabel->getStatuslabelType(),
]);
}
return Response::make(Response::error('Create failed: '.$statuslabel->getErrors()->first()));
}
public function schema(JsonSchema $schema): array
{
return [
'name' => $schema->string()->description('Status label name (required)'),
'type' => $schema->string()->description('Status label type: deployable, pending, archived, or undeployable (required)'),
'color' => $schema->string()->description('Display color in #RRGGBB format'),
'notes' => $schema->string()->description('Notes'),
'default_label' => $schema->boolean()->description('Whether this is the default label'),
'show_in_nav' => $schema->boolean()->description('Whether to show in navigation'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the status label was created'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the new status label'),
'name' => $schema->string()->description('Name of the new status label'),
'type' => $schema->string()->description('Type of the new status label'),
];
}
}
+96
View File
@@ -0,0 +1,96 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Supplier;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('create_supplier')]
#[Title('Create Supplier')]
#[Description('Create a new Snipe-IT supplier')]
class CreateSupplierTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('create', Supplier::class)) {
return Response::make(Response::error('Unauthorized'));
}
try {
$request->validate([
'name' => 'required|string|max:255',
'address' => 'nullable|string',
'address2' => 'nullable|string',
'city' => 'nullable|string',
'state' => 'nullable|string',
'country' => 'nullable|string',
'zip' => 'nullable|string',
'phone' => 'nullable|string',
'fax' => 'nullable|string',
'email' => 'nullable|email',
'url' => 'nullable|string',
'contact' => 'nullable|string',
'notes' => 'nullable|string',
]);
} catch (ValidationException $e) {
return Response::make(Response::error($e->validator->errors()->first()));
}
$supplier = new Supplier;
$supplier->fill($request->only([
'name', 'address', 'address2', 'city', 'state', 'country', 'zip',
'phone', 'fax', 'email', 'url', 'contact', 'notes',
]));
if ($supplier->save()) {
return Response::make(
Response::text('Supplier '.$supplier->name.' created successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Supplier created successfully',
'id' => $supplier->id,
'name' => $supplier->name,
]);
}
return Response::make(Response::error('Create failed: '.$supplier->getErrors()->first()));
}
public function schema(JsonSchema $schema): array
{
return [
'name' => $schema->string()->description('Supplier name (required)'),
'address' => $schema->string()->description('Address line 1'),
'address2' => $schema->string()->description('Address line 2'),
'city' => $schema->string()->description('City'),
'state' => $schema->string()->description('State'),
'country' => $schema->string()->description('Country'),
'zip' => $schema->string()->description('Postal code'),
'phone' => $schema->string()->description('Phone number'),
'fax' => $schema->string()->description('Fax number'),
'email' => $schema->string()->description('Email address'),
'url' => $schema->string()->description('Website URL'),
'contact' => $schema->string()->description('Contact name'),
'notes' => $schema->string()->description('Notes'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the supplier was created'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the new supplier'),
'name' => $schema->string()->description('Name of the new supplier'),
];
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
namespace App\Mcp\Tools;
use App\Models\AssetModel;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('delete_asset_model')]
#[Title('Delete Asset Model')]
#[Description('Soft-delete a Snipe-IT asset model by numeric ID or name')]
class DeleteAssetModelTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$model = $this->resolveModel($request);
if (! $model) {
return Response::make(Response::error('Asset model not found'));
}
if (! Gate::allows('delete', $model)) {
return Response::make(Response::error('Unauthorized'));
}
if ($model->assets()->count() > 0) {
return Response::make(Response::error('Model has assets and cannot be deleted'));
}
$name = $model->name;
$model->delete();
return Response::make(
Response::text('Asset model '.$name.' deleted successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Asset model deleted successfully',
'name' => $name,
]);
}
private function resolveModel(Request $request): ?AssetModel
{
if ($request->filled('id')) {
return AssetModel::find($request->get('id'));
}
if ($request->filled('name')) {
return AssetModel::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the asset model to delete'),
'name' => $schema->string()->description('Name of the asset model to delete'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the deletion succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'name' => $schema->string()->description('Name of the deleted asset model'),
];
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Category;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('delete_category')]
#[Title('Delete Category')]
#[Description('Soft-delete a Snipe-IT category. The category must have no items assigned to it.')]
class DeleteCategoryTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$category = $this->resolveCategory($request);
if (! $category) {
return Response::make(Response::error('Category not found'));
}
if (! Gate::allows('delete', $category)) {
return Response::make(Response::error('Unauthorized'));
}
$name = $category->name;
try {
$category->delete();
} catch (\Exception $e) {
return Response::make(Response::error('Category cannot be deleted: '.$e->getMessage()));
}
return Response::make(
Response::text('Category '.$name.' deleted successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Category deleted successfully',
'name' => $name,
]);
}
private function resolveCategory(Request $request): ?Category
{
if ($request->filled('id')) {
return Category::find($request->get('id'));
}
if ($request->filled('name')) {
return Category::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the category to delete'),
'name' => $schema->string()->description('Name of the category to delete'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the deletion succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'name' => $schema->string()->description('Name of the deleted category'),
];
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Company;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('delete_company')]
#[Title('Delete Company')]
#[Description('Soft-delete a Snipe-IT company by numeric ID or name')]
class DeleteCompanyTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$company = $this->resolveCompany($request);
if (! $company) {
return Response::make(Response::error('Company not found'));
}
if (! Gate::allows('delete', $company)) {
return Response::make(Response::error('Unauthorized'));
}
$name = $company->name;
$company->delete();
return Response::make(
Response::text('Company '.$name.' deleted successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Company deleted successfully',
'name' => $name,
]);
}
private function resolveCompany(Request $request): ?Company
{
if ($request->filled('id')) {
return Company::find($request->get('id'));
}
if ($request->filled('name')) {
return Company::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the company to delete'),
'name' => $schema->string()->description('Name of the company to delete'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the deletion succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'name' => $schema->string()->description('Name of the deleted company'),
];
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Consumable;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('delete_consumable')]
#[Title('Delete Consumable')]
#[Description('Soft-delete a Snipe-IT consumable. The consumable must have no units currently checked out.')]
class DeleteConsumableTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$consumable = $this->resolveConsumable($request);
if (! $consumable) {
return Response::make(Response::error('Consumable not found'));
}
if (! Gate::allows('delete', $consumable)) {
return Response::make(Response::error('Unauthorized'));
}
if ($consumable->users()->count() > 0) {
return Response::make(Response::error('Consumable has items checked out and cannot be deleted'));
}
$name = $consumable->name;
$consumable->delete();
return Response::make(
Response::text('Consumable '.$name.' deleted successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Consumable deleted successfully',
'name' => $name,
]);
}
private function resolveConsumable(Request $request): ?Consumable
{
if ($request->filled('id')) {
return Consumable::find($request->get('id'));
}
if ($request->filled('name')) {
return Consumable::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the consumable to delete'),
'name' => $schema->string()->description('Name of the consumable to delete'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the deletion succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'name' => $schema->string()->description('Name of the deleted consumable'),
];
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Depreciation;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('delete_depreciation')]
#[Title('Delete Depreciation')]
#[Description('Soft-delete a Snipe-IT depreciation schedule by numeric ID or name')]
class DeleteDepreciationTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$dep = $this->resolveDepreciation($request);
if (! $dep) {
return Response::make(Response::error('Depreciation not found'));
}
if (! Gate::allows('delete', $dep)) {
return Response::make(Response::error('Unauthorized'));
}
$name = $dep->name;
$dep->delete();
return Response::make(
Response::text('Depreciation '.$name.' deleted successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Depreciation deleted successfully',
'name' => $name,
]);
}
private function resolveDepreciation(Request $request): ?Depreciation
{
if ($request->filled('id')) {
return Depreciation::find($request->get('id'));
}
if ($request->filled('name')) {
return Depreciation::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the depreciation to delete'),
'name' => $schema->string()->description('Name of the depreciation to delete'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the deletion succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'name' => $schema->string()->description('Name of the deleted depreciation'),
];
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Group;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('delete_group')]
#[Title('Delete Group')]
#[Description('Delete a Snipe-IT permission group by ID or name. The group must have no users assigned.')]
class DeleteGroupTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('superadmin')) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
if ($request->filled('id')) {
$group = Group::find($request->get('id'));
} elseif ($request->filled('name')) {
$group = Group::where('name', $request->get('name'))->first();
} else {
return Response::make(Response::error('Please provide an id or name'));
}
if (! $group) {
return Response::make(Response::error('Group not found'));
}
$groupName = $group->name;
if ($group->delete()) {
return Response::make(
Response::text('Group '.$groupName.' deleted successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Group deleted successfully',
'name' => $groupName,
]);
}
return Response::make(Response::error('Delete failed'));
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric group ID to delete'),
'name' => $schema->string()->description('Group name to delete'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the deletion succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'name' => $schema->string()->description('Name of the deleted group'),
];
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Location;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('delete_location')]
#[Title('Delete Location')]
#[Description('Soft-delete a Snipe-IT location by numeric ID or name')]
class DeleteLocationTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$location = $this->resolveLocation($request);
if (! $location) {
return Response::make(Response::error('Location not found'));
}
if (! Gate::allows('delete', $location)) {
return Response::make(Response::error('Unauthorized'));
}
if ($location->users()->count() > 0) {
return Response::make(Response::error('Location has users assigned and cannot be deleted'));
}
if ($location->children()->count() > 0) {
return Response::make(Response::error('Location has child locations and cannot be deleted'));
}
$name = $location->name;
$location->delete();
return Response::make(
Response::text('Location '.$name.' deleted successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Location deleted successfully',
'name' => $name,
]);
}
private function resolveLocation(Request $request): ?Location
{
if ($request->filled('id')) {
return Location::find($request->get('id'));
}
if ($request->filled('name')) {
return Location::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the location to delete'),
'name' => $schema->string()->description('Name of the location to delete'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the deletion succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'name' => $schema->string()->description('Name of the deleted location'),
];
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Manufacturer;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('delete_manufacturer')]
#[Title('Delete Manufacturer')]
#[Description('Soft-delete a Snipe-IT manufacturer identified by numeric ID or name')]
class DeleteManufacturerTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$manufacturer = $this->resolveManufacturer($request);
if (! $manufacturer) {
return Response::make(Response::error('Manufacturer not found'));
}
if (! Gate::allows('delete', $manufacturer)) {
return Response::make(Response::error('Unauthorized'));
}
$name = $manufacturer->name;
$manufacturer->delete();
return Response::make(
Response::text('Manufacturer '.$name.' deleted successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Manufacturer deleted successfully',
'name' => $name,
]);
}
private function resolveManufacturer(Request $request): ?Manufacturer
{
if ($request->filled('id')) {
return Manufacturer::find($request->get('id'));
}
if ($request->filled('name')) {
return Manufacturer::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the manufacturer to delete'),
'name' => $schema->string()->description('Name of the manufacturer to delete'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the deletion succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'name' => $schema->string()->description('Name of the deleted manufacturer'),
];
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Statuslabel;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('delete_status_label')]
#[Title('Delete Status Label')]
#[Description('Soft-delete a Snipe-IT status label identified by numeric ID or name')]
class DeleteStatusLabelTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$label = $this->resolveStatusLabel($request);
if (! $label) {
return Response::make(Response::error('Status label not found'));
}
if (! Gate::allows('delete', $label)) {
return Response::make(Response::error('Unauthorized'));
}
if ($label->assets()->count() > 0) {
return Response::make(Response::error('Status label has assets assigned and cannot be deleted'));
}
$name = $label->name;
$label->delete();
return Response::make(
Response::text('Status label '.$name.' deleted successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Status label deleted successfully',
'name' => $name,
]);
}
private function resolveStatusLabel(Request $request): ?Statuslabel
{
if ($request->filled('id')) {
return Statuslabel::find($request->get('id'));
}
if ($request->filled('name')) {
return Statuslabel::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the status label to delete'),
'name' => $schema->string()->description('Name of the status label to delete'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the deletion succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'name' => $schema->string()->description('Name of the deleted status label'),
];
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Supplier;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('delete_supplier')]
#[Title('Delete Supplier')]
#[Description('Soft-delete a Snipe-IT supplier identified by numeric ID or name')]
class DeleteSupplierTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$supplier = $this->resolveSupplier($request);
if (! $supplier) {
return Response::make(Response::error('Supplier not found'));
}
if (! Gate::allows('delete', $supplier)) {
return Response::make(Response::error('Unauthorized'));
}
$name = $supplier->name;
$supplier->delete();
return Response::make(
Response::text('Supplier '.$name.' deleted successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Supplier deleted successfully',
'name' => $name,
]);
}
private function resolveSupplier(Request $request): ?Supplier
{
if ($request->filled('id')) {
return Supplier::find($request->get('id'));
}
if ($request->filled('name')) {
return Supplier::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the supplier to delete'),
'name' => $schema->string()->description('Name of the supplier to delete'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the deletion succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'name' => $schema->string()->description('Name of the deleted supplier'),
];
}
}
+102
View File
@@ -0,0 +1,102 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Actionlog;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('get_activity_log')]
#[Title('Get Activity Log')]
#[Description('Retrieve the Snipe-IT activity log with optional filtering by item type, item ID, user, and action type')]
class GetActivityLogTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('activity.view')) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'item_type' => 'nullable|string|max:255',
'item_id' => 'nullable|integer',
'user_id' => 'nullable|integer',
'action_type' => 'nullable|string|max:255',
'limit' => 'nullable|integer|min:1|max:500',
'offset' => 'nullable|integer|min:0',
]);
$logs = Actionlog::with('user', 'item')->orderBy('created_at', 'desc');
if ($request->filled('item_type')) {
$logs->where('item_type', $request->get('item_type'));
}
if ($request->filled('item_id')) {
$logs->where('item_id', $request->get('item_id'));
}
if ($request->filled('user_id')) {
$logs->where('user_id', $request->get('user_id'));
}
if ($request->filled('action_type')) {
$logs->where('action_type', $request->get('action_type'));
}
$total = $logs->count();
$limit = $request->filled('limit') ? (int) $request->get('limit') : 25;
$offset = $request->filled('offset') ? (int) $request->get('offset') : 0;
$results = $logs->skip($offset)->take($limit)->get();
$activityData = $results->map(fn (Actionlog $log) => [
'id' => $log->id,
'action_type' => $log->action_type,
'item_type' => $log->item_type,
'item_id' => $log->item_id,
'user_id' => $log->user_id,
'user' => $log->user?->username,
'note' => $log->note,
'created_at' => $log->created_at?->toDateTimeString(),
])->values()->all();
return Response::make(
Response::text("Found {$total} activity log entries, returning ".count($activityData))
)->withStructuredContent([
'total' => $total,
'offset' => $offset,
'limit' => $limit,
'activity' => $activityData,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'item_type' => $schema->string()->description('Filter by item type (e.g. App\\Models\\Asset)'),
'item_id' => $schema->number()->description('Filter by item ID'),
'user_id' => $schema->number()->description('Filter by user ID'),
'action_type' => $schema->string()->description('Filter by action type (e.g. checkout, checkin, update)'),
'limit' => $schema->number()->description('Number of results to return (default: 25, max: 500)'),
'offset' => $schema->number()->description('Number of results to skip for pagination (default: 0)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'total' => $schema->number()->description('Total number of matching log entries')->required(),
'offset' => $schema->number()->description('Current pagination offset')->required(),
'limit' => $schema->number()->description('Results per page')->required(),
'activity' => $schema->array()->description('List of activity log entries')->required(),
];
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace App\Mcp\Tools;
use App\Models\User;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('get_current_user')]
#[Title('Get Current User')]
#[Description('Return information about the currently authenticated Snipe-IT user')]
class GetCurrentUserTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! auth()->check()) {
return Response::make(Response::error('Not authenticated'));
}
$user = User::with('company', 'department', 'userloc')->find(auth()->id());
if (! $user) {
return Response::make(Response::error('Not authenticated'));
}
return Response::make(
Response::text('Current user: '.$user->username)
)->withStructuredContent([
'id' => $user->id,
'username' => $user->username,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'email' => $user->email,
'company' => $user->company?->name,
'department' => $user->department?->name,
'location' => $user->userloc?->name,
'employee_num' => $user->employee_num,
'title' => $user->jobtitle,
'phone' => $user->phone,
'activated' => (bool) $user->activated,
]);
}
public function schema(JsonSchema $schema): array
{
return [];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric user ID')->required(),
'username' => $schema->string()->description('Username')->required(),
'first_name' => $schema->string()->description('First name'),
'last_name' => $schema->string()->description('Last name'),
'email' => $schema->string()->description('Email address'),
'company' => $schema->string()->description('Company name'),
'department' => $schema->string()->description('Department name'),
'location' => $schema->string()->description('Default location name'),
'employee_num' => $schema->string()->description('Employee number'),
'title' => $schema->string()->description('Job title'),
'phone' => $schema->string()->description('Phone number'),
'activated' => $schema->boolean()->description('Whether the account is activated'),
];
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Asset;
use App\Models\User;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('get_user_assets')]
#[Title('Get User Assets')]
#[Description('Return all assets currently checked out to a Snipe-IT user')]
class GetUserAssetsTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('view', User::class)) {
return Response::make(Response::error('Unauthorized'));
}
if (! Gate::allows('view', Asset::class)) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'id' => 'required|integer',
]);
$user = User::find($request->get('id'));
if (! $user) {
return Response::make(Response::error('User not found'));
}
$assets = Asset::where('assigned_to', $user->id)
->where('assigned_type', User::class)
->with('model', 'status', 'location')
->get();
$data = $assets->map(fn ($asset) => [
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
'name' => $asset->name,
'serial' => $asset->serial,
'model' => $asset->model?->name,
'status' => $asset->status?->name,
])->values()->all();
return Response::make(
Response::text('Found '.count($data).' assets for user '.$user->username)
)->withStructuredContent([
'user_id' => $user->id,
'username' => $user->username,
'total' => count($data),
'assets' => $data,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the user whose assets should be listed (required)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'user_id' => $schema->number()->description('Numeric ID of the user')->required(),
'username' => $schema->string()->description('Username of the user')->required(),
'total' => $schema->number()->description('Total number of assets checked out to the user')->required(),
'assets' => $schema->array()->description('List of checked-out assets'),
];
}
}
+101
View File
@@ -0,0 +1,101 @@
<?php
namespace App\Mcp\Tools;
use App\Models\AssetModel;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('list_asset_models')]
#[Title('List Asset Models')]
#[Description('Search and list Snipe-IT asset models with optional filtering and pagination')]
class ListAssetModelsTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('view', AssetModel::class)) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'search' => 'nullable|string|max:255',
'category_id' => 'nullable|integer',
'manufacturer_id' => 'nullable|integer',
'limit' => 'nullable|integer|min:1|max:500',
'offset' => 'nullable|integer|min:0',
]);
$models = AssetModel::with('category', 'manufacturer', 'depreciation')
->withCount('assets as assets_count');
if ($request->filled('search')) {
$models->TextSearch($request->get('search'));
}
if ($request->filled('category_id')) {
$models->where('category_id', $request->get('category_id'));
}
if ($request->filled('manufacturer_id')) {
$models->where('manufacturer_id', $request->get('manufacturer_id'));
}
$models->orderBy('models.created_at', 'desc');
$total = $models->count();
$limit = $request->filled('limit') ? (int) $request->get('limit') : 25;
$offset = $request->filled('offset') ? (int) $request->get('offset') : 0;
$results = $models->skip($offset)->take($limit)->get();
$modelsData = $results->map(fn (AssetModel $model) => [
'id' => $model->id,
'name' => $model->name,
'model_number' => $model->model_number,
'category_id' => $model->category_id,
'category' => $model->category?->name,
'manufacturer_id' => $model->manufacturer_id,
'manufacturer' => $model->manufacturer?->name,
'assets_count' => $model->assets_count,
'eol' => $model->eol,
'min_amt' => $model->min_amt,
])->values()->all();
return Response::make(
Response::text("Found {$total} asset models, returning ".count($modelsData))
)->withStructuredContent([
'total' => $total,
'offset' => $offset,
'limit' => $limit,
'models' => $modelsData,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'search' => $schema->string()->description('Keyword to search across model name, model number'),
'category_id' => $schema->number()->description('Filter by category ID'),
'manufacturer_id' => $schema->number()->description('Filter by manufacturer ID'),
'limit' => $schema->number()->description('Number of results to return (default: 25, max: 500)'),
'offset' => $schema->number()->description('Number of results to skip for pagination (default: 0)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'total' => $schema->number()->description('Total number of matching asset models')->required(),
'offset' => $schema->number()->description('Current pagination offset')->required(),
'limit' => $schema->number()->description('Results per page')->required(),
'models' => $schema->array()->description('List of asset models')->required(),
];
}
}
+98
View File
@@ -0,0 +1,98 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Category;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('list_categories')]
#[Title('List Categories')]
#[Description('Search and list Snipe-IT categories with optional filtering by type and pagination')]
class ListCategoriesTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('view', Category::class)) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'search' => 'nullable|string|max:255',
'category_type' => 'nullable|string|in:asset,accessory,consumable,component,license',
'limit' => 'nullable|integer|min:1|max:500',
'offset' => 'nullable|integer|min:0',
]);
$categories = Category::withCount(
'showableAssets as assets_count',
'accessories as accessories_count',
'consumables as consumables_count',
'components as components_count',
'licenses as licenses_count'
);
if ($request->filled('search')) {
$categories->TextSearch($request->get('search'));
}
if ($request->filled('category_type')) {
$categories->where('category_type', $request->get('category_type'));
}
$categories->orderBy('created_at', 'desc');
$total = $categories->count();
$limit = $request->filled('limit') ? (int) $request->get('limit') : 25;
$offset = $request->filled('offset') ? (int) $request->get('offset') : 0;
$results = $categories->skip($offset)->take($limit)->get();
$categoriesData = $results->map(fn (Category $category) => [
'id' => $category->id,
'name' => $category->name,
'category_type' => $category->category_type,
'assets_count' => $category->assets_count,
'accessories_count' => $category->accessories_count,
'consumables_count' => $category->consumables_count,
'components_count' => $category->components_count,
'licenses_count' => $category->licenses_count,
])->values()->all();
return Response::make(
Response::text("Found {$total} categories, returning ".count($categoriesData))
)->withStructuredContent([
'total' => $total,
'offset' => $offset,
'limit' => $limit,
'categories' => $categoriesData,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'search' => $schema->string()->description('Keyword to search across category name, type, notes'),
'category_type' => $schema->string()->description('Filter by type: asset, accessory, consumable, component, or license'),
'limit' => $schema->number()->description('Number of results to return (default: 25, max: 500)'),
'offset' => $schema->number()->description('Number of results to skip for pagination (default: 0)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'total' => $schema->number()->description('Total number of matching categories')->required(),
'offset' => $schema->number()->description('Current pagination offset')->required(),
'limit' => $schema->number()->description('Results per page')->required(),
'categories' => $schema->array()->description('List of categories')->required(),
];
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Company;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('list_companies')]
#[Title('List Companies')]
#[Description('Search and list Snipe-IT companies with optional filtering and pagination')]
class ListCompaniesTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('view', Company::class)) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'search' => 'nullable|string|max:255',
'limit' => 'nullable|integer|min:1|max:500',
'offset' => 'nullable|integer|min:0',
]);
$companies = Company::withCount([
'assets as assets_count' => fn ($q) => $q->AssetsForShow(),
])->withCount('licenses as licenses_count', 'users as users_count');
if ($request->filled('search')) {
$companies->TextSearch($request->get('search'));
}
$companies->orderBy('created_at', 'desc');
$total = $companies->count();
$limit = $request->filled('limit') ? (int) $request->get('limit') : 25;
$offset = $request->filled('offset') ? (int) $request->get('offset') : 0;
$results = $companies->skip($offset)->take($limit)->get();
$companiesData = $results->map(fn (Company $company) => [
'id' => $company->id,
'name' => $company->name,
'phone' => $company->phone,
'fax' => $company->fax,
'email' => $company->email,
'assets_count' => $company->assets_count,
'licenses_count' => $company->licenses_count,
'users_count' => $company->users_count,
])->values()->all();
return Response::make(
Response::text("Found {$total} companies, returning ".count($companiesData))
)->withStructuredContent([
'total' => $total,
'offset' => $offset,
'limit' => $limit,
'companies' => $companiesData,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'search' => $schema->string()->description('Keyword to search across company name, phone, fax, email'),
'limit' => $schema->number()->description('Number of results to return (default: 25, max: 500)'),
'offset' => $schema->number()->description('Number of results to skip for pagination (default: 0)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'total' => $schema->number()->description('Total number of matching companies')->required(),
'offset' => $schema->number()->description('Current pagination offset')->required(),
'limit' => $schema->number()->description('Results per page')->required(),
'companies' => $schema->array()->description('List of companies')->required(),
];
}
}
+110
View File
@@ -0,0 +1,110 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Consumable;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('list_consumables')]
#[Title('List Consumables')]
#[Description('Search and list Snipe-IT consumables with optional filtering by keyword, company, category, manufacturer, location, and pagination')]
class ListConsumablesTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('index', Consumable::class)) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'search' => 'nullable|string|max:255',
'company_id' => 'nullable|integer',
'category_id' => 'nullable|integer',
'manufacturer_id' => 'nullable|integer',
'location_id' => 'nullable|integer',
'limit' => 'nullable|integer|min:1|max:500',
'offset' => 'nullable|integer|min:0',
]);
$consumables = Consumable::with('company', 'category', 'manufacturer', 'supplier', 'location')
->withCount('users as users_count');
if ($request->filled('search')) {
$consumables->TextSearch($request->get('search'));
}
if ($request->filled('company_id')) {
$consumables->where('consumables.company_id', $request->get('company_id'));
}
if ($request->filled('category_id')) {
$consumables->where('category_id', $request->get('category_id'));
}
if ($request->filled('manufacturer_id')) {
$consumables->where('manufacturer_id', $request->get('manufacturer_id'));
}
if ($request->filled('location_id')) {
$consumables->where('location_id', $request->get('location_id'));
}
$total = $consumables->count();
$limit = $request->filled('limit') ? (int) $request->get('limit') : 25;
$offset = $request->filled('offset') ? (int) $request->get('offset') : 0;
$results = $consumables->orderBy('consumables.created_at', 'desc')->skip($offset)->take($limit)->get();
$consumablesData = $results->map(fn (Consumable $consumable) => [
'id' => $consumable->id,
'name' => $consumable->name,
'qty' => $consumable->qty,
'users_count' => $consumable->users_count,
'category' => $consumable->category?->name,
'manufacturer' => $consumable->manufacturer?->name,
'company' => $consumable->company?->name,
'location' => $consumable->location?->name,
'purchase_cost' => $consumable->purchase_cost,
'purchase_date' => $consumable->purchase_date?->format('Y-m-d'),
])->values()->all();
return Response::make(
Response::text("Found {$total} consumables, returning ".count($consumablesData))
)->withStructuredContent([
'total' => $total,
'offset' => $offset,
'limit' => $limit,
'consumables' => $consumablesData,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'search' => $schema->string()->description('Keyword to search across consumable name and other fields'),
'company_id' => $schema->number()->description('Filter by company ID'),
'category_id' => $schema->number()->description('Filter by category ID'),
'manufacturer_id' => $schema->number()->description('Filter by manufacturer ID'),
'location_id' => $schema->number()->description('Filter by location ID'),
'limit' => $schema->number()->description('Number of results to return (default: 25, max: 500)'),
'offset' => $schema->number()->description('Number of results to skip for pagination (default: 0)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'total' => $schema->number()->description('Total number of matching consumables')->required(),
'offset' => $schema->number()->description('Current pagination offset')->required(),
'limit' => $schema->number()->description('Results per page')->required(),
];
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Depreciation;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('list_depreciations')]
#[Title('List Depreciations')]
#[Description('Search and list Snipe-IT depreciation schedules with optional filtering and pagination')]
class ListDepreciationsTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('view', Depreciation::class)) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'search' => 'nullable|string|max:255',
'limit' => 'nullable|integer|min:1|max:500',
'offset' => 'nullable|integer|min:0',
]);
$depreciations = Depreciation::withCount('models as models_count');
if ($request->filled('search')) {
$depreciations->TextSearch($request->get('search'));
}
$depreciations->orderBy('created_at', 'desc');
$total = $depreciations->count();
$limit = $request->filled('limit') ? (int) $request->get('limit') : 25;
$offset = $request->filled('offset') ? (int) $request->get('offset') : 0;
$results = $depreciations->skip($offset)->take($limit)->get();
$depreciationsData = $results->map(fn (Depreciation $dep) => [
'id' => $dep->id,
'name' => $dep->name,
'months' => $dep->months,
'models_count' => $dep->models_count,
])->values()->all();
return Response::make(
Response::text("Found {$total} depreciations, returning ".count($depreciationsData))
)->withStructuredContent([
'total' => $total,
'offset' => $offset,
'limit' => $limit,
'depreciations' => $depreciationsData,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'search' => $schema->string()->description('Keyword to search across depreciation name'),
'limit' => $schema->number()->description('Number of results to return (default: 25, max: 500)'),
'offset' => $schema->number()->description('Number of results to skip for pagination (default: 0)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'total' => $schema->number()->description('Total number of matching depreciations')->required(),
'offset' => $schema->number()->description('Current pagination offset')->required(),
'limit' => $schema->number()->description('Results per page')->required(),
'depreciations' => $schema->array()->description('List of depreciation schedules')->required(),
];
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Group;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('list_groups')]
#[Title('List Groups')]
#[Description('List Snipe-IT permission groups with optional search and pagination')]
class ListGroupsTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('superadmin')) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'search' => 'nullable|string|max:255',
'limit' => 'nullable|integer|min:1|max:500',
'offset' => 'nullable|integer|min:0',
]);
$groups = Group::withCount('users as users_count')
->orderBy('created_at', 'desc');
if ($request->filled('search')) {
$groups->TextSearch($request->get('search'));
}
$total = $groups->count();
$limit = $request->filled('limit') ? (int) $request->get('limit') : 25;
$offset = $request->filled('offset') ? (int) $request->get('offset') : 0;
$results = $groups->skip($offset)->take($limit)->get();
$groupsData = $results->map(fn (Group $group) => [
'id' => $group->id,
'name' => $group->name,
'notes' => $group->notes,
'users_count' => $group->users_count,
])->values()->all();
return Response::make(
Response::text("Found {$total} groups, returning ".count($groupsData))
)->withStructuredContent([
'total' => $total,
'offset' => $offset,
'limit' => $limit,
'groups' => $groupsData,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'search' => $schema->string()->description('Keyword to search groups by name or notes'),
'limit' => $schema->number()->description('Number of results to return (default: 25, max: 500)'),
'offset' => $schema->number()->description('Number of results to skip for pagination (default: 0)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'total' => $schema->number()->description('Total number of matching groups')->required(),
'offset' => $schema->number()->description('Current pagination offset')->required(),
'limit' => $schema->number()->description('Results per page')->required(),
'groups' => $schema->array()->description('List of groups')->required(),
];
}
}
+101
View File
@@ -0,0 +1,101 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Location;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('list_locations')]
#[Title('List Locations')]
#[Description('Search and list Snipe-IT locations with optional filtering and pagination')]
class ListLocationsTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('view', Location::class)) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'search' => 'nullable|string|max:255',
'parent_id' => 'nullable|integer',
'limit' => 'nullable|integer|min:1|max:500',
'offset' => 'nullable|integer|min:0',
]);
$locations = Location::with('parent')->withCount(
'assets as assets_count',
'users as users_count',
'children as children_count'
);
if ($request->filled('search')) {
$locations->TextSearch($request->get('search'));
}
if ($request->filled('parent_id')) {
$locations->where('parent_id', $request->get('parent_id'));
}
$locations->orderBy('created_at', 'desc');
$total = $locations->count();
$limit = $request->filled('limit') ? (int) $request->get('limit') : 25;
$offset = $request->filled('offset') ? (int) $request->get('offset') : 0;
$results = $locations->skip($offset)->take($limit)->get();
$locationsData = $results->map(fn (Location $location) => [
'id' => $location->id,
'name' => $location->name,
'address' => $location->address,
'city' => $location->city,
'state' => $location->state,
'country' => $location->country,
'zip' => $location->zip,
'phone' => $location->phone,
'parent_id' => $location->parent_id,
'parent' => $location->parent?->name,
'assets_count' => $location->assets_count,
'users_count' => $location->users_count,
'children_count' => $location->children_count,
])->values()->all();
return Response::make(
Response::text("Found {$total} locations, returning ".count($locationsData))
)->withStructuredContent([
'total' => $total,
'offset' => $offset,
'limit' => $limit,
'locations' => $locationsData,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'search' => $schema->string()->description('Keyword to search across location name, city, state, country'),
'parent_id' => $schema->number()->description('Filter by parent location ID'),
'limit' => $schema->number()->description('Number of results to return (default: 25, max: 500)'),
'offset' => $schema->number()->description('Number of results to skip for pagination (default: 0)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'total' => $schema->number()->description('Total number of matching locations')->required(),
'offset' => $schema->number()->description('Current pagination offset')->required(),
'limit' => $schema->number()->description('Results per page')->required(),
'locations' => $schema->array()->description('List of locations')->required(),
];
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Asset;
use App\Models\Maintenance;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('list_maintenances')]
#[Title('List Maintenances')]
#[Description('List asset maintenances with optional filtering by asset and pagination')]
class ListMaintenancesTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('view', Asset::class)) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'asset_id' => 'nullable|integer',
'limit' => 'nullable|integer|min:1|max:500',
'offset' => 'nullable|integer|min:0',
]);
$maintenances = Maintenance::with('asset', 'supplier');
if ($request->filled('asset_id')) {
$maintenances->where('asset_id', $request->get('asset_id'));
}
$maintenances->orderBy('created_at', 'desc');
$total = $maintenances->count();
$limit = $request->filled('limit') ? (int) $request->get('limit') : 25;
$offset = $request->filled('offset') ? (int) $request->get('offset') : 0;
$results = $maintenances->skip($offset)->take($limit)->get();
$maintenancesData = $results->map(fn (Maintenance $maintenance) => [
'id' => $maintenance->id,
'title' => $maintenance->name,
'asset_id' => $maintenance->asset_id,
'asset_tag' => $maintenance->asset?->asset_tag,
'is_warranty' => (bool) $maintenance->is_warranty,
'cost' => $maintenance->cost,
'start_date' => $maintenance->start_date,
'completion_date' => $maintenance->completion_date,
'supplier' => $maintenance->supplier?->name,
'notes' => $maintenance->notes,
])->values()->all();
return Response::make(
Response::text("Found {$total} maintenances, returning ".count($maintenancesData))
)->withStructuredContent([
'total' => $total,
'offset' => $offset,
'limit' => $limit,
'maintenances' => $maintenancesData,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'asset_id' => $schema->number()->description('Filter by asset ID'),
'limit' => $schema->number()->description('Number of results to return (default: 25, max: 500)'),
'offset' => $schema->number()->description('Number of results to skip for pagination (default: 0)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'total' => $schema->number()->description('Total number of matching maintenances')->required(),
'offset' => $schema->number()->description('Current pagination offset')->required(),
'limit' => $schema->number()->description('Results per page')->required(),
'maintenances' => $schema->array()->description('List of maintenances')->required(),
];
}
}
+93
View File
@@ -0,0 +1,93 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Manufacturer;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('list_manufacturers')]
#[Title('List Manufacturers')]
#[Description('Search and list Snipe-IT manufacturers with optional filtering and pagination')]
class ListManufacturersTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('view', Manufacturer::class)) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'search' => 'nullable|string|max:255',
'limit' => 'nullable|integer|min:1|max:500',
'offset' => 'nullable|integer|min:0',
]);
$manufacturers = Manufacturer::withCount(
'assets as assets_count',
'licenses as licenses_count',
'accessories as accessories_count',
'components as components_count'
);
if ($request->filled('search')) {
$manufacturers->TextSearch($request->get('search'));
}
$manufacturers->orderBy('created_at', 'desc');
$total = $manufacturers->count();
$limit = $request->filled('limit') ? (int) $request->get('limit') : 25;
$offset = $request->filled('offset') ? (int) $request->get('offset') : 0;
$results = $manufacturers->skip($offset)->take($limit)->get();
$manufacturersData = $results->map(fn (Manufacturer $manufacturer) => [
'id' => $manufacturer->id,
'name' => $manufacturer->name,
'url' => $manufacturer->url,
'support_url' => $manufacturer->support_url,
'support_email' => $manufacturer->support_email,
'support_phone' => $manufacturer->support_phone,
'assets_count' => $manufacturer->assets_count,
'licenses_count' => $manufacturer->licenses_count,
'accessories_count' => $manufacturer->accessories_count,
'components_count' => $manufacturer->components_count,
])->values()->all();
return Response::make(
Response::text("Found {$total} manufacturers, returning ".count($manufacturersData))
)->withStructuredContent([
'total' => $total,
'offset' => $offset,
'limit' => $limit,
'manufacturers' => $manufacturersData,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'search' => $schema->string()->description('Keyword to search across manufacturer name and notes'),
'limit' => $schema->number()->description('Number of results to return (default: 25, max: 500)'),
'offset' => $schema->number()->description('Number of results to skip for pagination (default: 0)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'total' => $schema->number()->description('Total number of matching manufacturers')->required(),
'offset' => $schema->number()->description('Current pagination offset')->required(),
'limit' => $schema->number()->description('Results per page')->required(),
'manufacturers' => $schema->array()->description('List of manufacturers')->required(),
];
}
}
+99
View File
@@ -0,0 +1,99 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Statuslabel;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('list_status_labels')]
#[Title('List Status Labels')]
#[Description('Search and list Snipe-IT status labels with optional filtering and pagination')]
class ListStatusLabelsTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('view', Statuslabel::class)) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'search' => 'nullable|string|max:255',
'status_type' => 'nullable|string|in:deployable,pending,archived,undeployable',
'limit' => 'nullable|integer|min:1|max:500',
'offset' => 'nullable|integer|min:0',
]);
$labels = Statuslabel::withCount('assets as assets_count');
if ($request->filled('search')) {
$labels->TextSearch($request->get('search'));
}
if ($request->filled('status_type')) {
$type = $request->get('status_type');
if ($type === 'deployable') {
$labels->Deployable();
} elseif ($type === 'pending') {
$labels->Pending();
} elseif ($type === 'archived') {
$labels->Archived();
} elseif ($type === 'undeployable') {
$labels->Undeployable();
}
}
$total = $labels->count();
$limit = $request->filled('limit') ? (int) $request->get('limit') : 25;
$offset = $request->filled('offset') ? (int) $request->get('offset') : 0;
$results = $labels->skip($offset)->take($limit)->get();
$labelsData = $results->map(fn (Statuslabel $label) => [
'id' => $label->id,
'name' => $label->name,
'type' => $label->getStatuslabelType(),
'color' => $label->color,
'assets_count' => $label->assets_count,
'deployable' => $label->deployable,
'pending' => $label->pending,
'archived' => $label->archived,
])->values()->all();
return Response::make(
Response::text("Found {$total} status labels, returning ".count($labelsData))
)->withStructuredContent([
'total' => $total,
'offset' => $offset,
'limit' => $limit,
'status_labels' => $labelsData,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'search' => $schema->string()->description('Keyword to search across status label name and notes'),
'status_type' => $schema->string()->description('Filter by type: deployable, pending, archived, undeployable'),
'limit' => $schema->number()->description('Number of results to return (default: 25, max: 500)'),
'offset' => $schema->number()->description('Number of results to skip for pagination (default: 0)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'total' => $schema->number()->description('Total number of matching status labels')->required(),
'offset' => $schema->number()->description('Current pagination offset')->required(),
'limit' => $schema->number()->description('Results per page')->required(),
'status_labels' => $schema->array()->description('List of status labels')->required(),
];
}
}
+92
View File
@@ -0,0 +1,92 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Supplier;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('list_suppliers')]
#[Title('List Suppliers')]
#[Description('Search and list Snipe-IT suppliers with optional filtering and pagination')]
class ListSuppliersTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('view', Supplier::class)) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'search' => 'nullable|string|max:255',
'limit' => 'nullable|integer|min:1|max:500',
'offset' => 'nullable|integer|min:0',
]);
$suppliers = Supplier::withCount(
'assets as assets_count',
'licenses as licenses_count'
);
if ($request->filled('search')) {
$suppliers->TextSearch($request->get('search'));
}
$suppliers->orderBy('created_at', 'desc');
$total = $suppliers->count();
$limit = $request->filled('limit') ? (int) $request->get('limit') : 25;
$offset = $request->filled('offset') ? (int) $request->get('offset') : 0;
$results = $suppliers->skip($offset)->take($limit)->get();
$suppliersData = $results->map(fn (Supplier $supplier) => [
'id' => $supplier->id,
'name' => $supplier->name,
'address' => $supplier->address,
'city' => $supplier->city,
'state' => $supplier->state,
'country' => $supplier->country,
'phone' => $supplier->phone,
'email' => $supplier->email,
'url' => $supplier->url,
'assets_count' => $supplier->assets_count,
'licenses_count' => $supplier->licenses_count,
])->values()->all();
return Response::make(
Response::text("Found {$total} suppliers, returning ".count($suppliersData))
)->withStructuredContent([
'total' => $total,
'offset' => $offset,
'limit' => $limit,
'suppliers' => $suppliersData,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'search' => $schema->string()->description('Keyword to search across supplier fields'),
'limit' => $schema->number()->description('Number of results to return (default: 25, max: 500)'),
'offset' => $schema->number()->description('Number of results to skip for pagination (default: 0)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'total' => $schema->number()->description('Total number of matching suppliers')->required(),
'offset' => $schema->number()->description('Current pagination offset')->required(),
'limit' => $schema->number()->description('Results per page')->required(),
'suppliers' => $schema->array()->description('List of suppliers')->required(),
];
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace App\Mcp\Tools;
use App\Models\User;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('reset_2fa')]
#[Title('Reset Two-Factor Authentication')]
#[Description('Reset two-factor authentication for a Snipe-IT user')]
class Reset2FATool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('update', User::class)) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'id' => 'required|integer',
]);
$user = User::find($request->get('id'));
if (! $user) {
return Response::make(Response::error('User not found'));
}
$user->two_factor_secret = null;
$user->two_factor_enrolled = 0;
$user->two_factor_optin = 0;
$user->save();
return Response::make(
Response::text('Two-factor authentication reset for '.$user->username)
)->withStructuredContent([
'success' => true,
'message' => 'Two-factor authentication reset successfully',
'id' => $user->id,
'username' => $user->username,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the user whose 2FA should be reset (required)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the reset succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the user'),
'username' => $schema->string()->description('Username of the user'),
];
}
}
+69
View File
@@ -0,0 +1,69 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Asset;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('restore_asset')]
#[Title('Restore Asset')]
#[Description('Restore a soft-deleted Snipe-IT asset')]
class RestoreAssetTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'required|integer',
]);
$asset = Asset::withTrashed()->find($request->get('id'));
if (! $asset) {
return Response::make(Response::error('Asset not found'));
}
if (! $asset->deleted_at) {
return Response::make(Response::error('Asset is not deleted'));
}
if (! Gate::allows('delete', Asset::class)) {
return Response::make(Response::error('Unauthorized'));
}
$asset->restore();
return Response::make(
Response::text('Asset '.$asset->asset_tag.' restored successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Asset restored successfully',
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the asset to restore (required)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the restore succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the restored asset'),
'asset_tag' => $schema->string()->description('Asset tag of the restored asset'),
];
}
}
+73
View File
@@ -0,0 +1,73 @@
<?php
namespace App\Mcp\Tools;
use App\Models\User;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('restore_user')]
#[Title('Restore User')]
#[Description('Restore a soft-deleted Snipe-IT user')]
class RestoreUserTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('delete', User::class)) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'id' => 'required|integer',
]);
$user = User::withTrashed()->find($request->get('id'));
if (! $user) {
return Response::make(Response::error('User not found'));
}
if (! $user->deleted_at) {
return Response::make(Response::error('User is not deleted'));
}
if (! Gate::allows('delete', User::class)) {
return Response::make(Response::error('Unauthorized'));
}
$user->restore();
return Response::make(
Response::text('User '.$user->username.' restored successfully')
)->withStructuredContent([
'success' => true,
'message' => 'User restored successfully',
'id' => $user->id,
'username' => $user->username,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the user to restore (required)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the restore succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the restored user'),
'username' => $schema->string()->description('Username of the restored user'),
];
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
namespace App\Mcp\Tools;
use App\Models\AssetModel;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('show_asset_model')]
#[Title('Show Asset Model Details')]
#[Description('Look up a single Snipe-IT asset model by numeric ID or name and return its full details')]
class ShowAssetModelTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$model = $this->resolveModel($request);
if ($model === false) {
return Response::make(Response::error('Please provide an id or name'));
}
if (! $model) {
return Response::make(Response::error('Asset model not found'));
}
if (! Gate::allows('view', $model)) {
return Response::make(Response::error('Unauthorized'));
}
$model->loadCount('assets as assets_count');
return Response::make(
Response::text('Asset model '.$model->name.' found')
)->withStructuredContent([
'id' => $model->id,
'name' => $model->name,
'model_number' => $model->model_number,
'category_id' => $model->category_id,
'category' => $model->category?->name,
'manufacturer_id' => $model->manufacturer_id,
'manufacturer' => $model->manufacturer?->name,
'depreciation_id' => $model->depreciation_id,
'depreciation' => $model->depreciation?->name,
'assets_count' => $model->assets_count,
'eol' => $model->eol,
'min_amt' => $model->min_amt,
'notes' => $model->notes,
'created_at' => $model->created_at?->format('Y-m-d H:i:s'),
'updated_at' => $model->updated_at?->format('Y-m-d H:i:s'),
]);
}
private function resolveModel(Request $request): AssetModel|false|null
{
if ($request->filled('id')) {
return AssetModel::with('category', 'manufacturer', 'depreciation')->find($request->get('id'));
}
if ($request->filled('name')) {
return AssetModel::with('category', 'manufacturer', 'depreciation')->where('name', $request->get('name'))->first();
}
return false;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the asset model to look up'),
'name' => $schema->string()->description('Name of the asset model to look up'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric asset model ID'),
'name' => $schema->string()->description('Asset model name'),
'model_number' => $schema->string()->description('Model number'),
'category_id' => $schema->number()->description('Category ID'),
'category' => $schema->string()->description('Category name'),
'manufacturer_id' => $schema->number()->description('Manufacturer ID'),
'manufacturer' => $schema->string()->description('Manufacturer name'),
'depreciation_id' => $schema->number()->description('Depreciation schedule ID'),
'depreciation' => $schema->string()->description('Depreciation schedule name'),
'assets_count' => $schema->number()->description('Number of assets using this model'),
'eol' => $schema->number()->description('End of life in months'),
'min_amt' => $schema->number()->description('Minimum quantity alert threshold'),
'notes' => $schema->string()->description('Notes'),
'created_at' => $schema->string()->description('Creation timestamp'),
'updated_at' => $schema->string()->description('Last update timestamp'),
];
}
}
+95
View File
@@ -0,0 +1,95 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Category;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('show_category')]
#[Title('Show Category Details')]
#[Description('Look up a single Snipe-IT category by numeric ID or name and return its full details')]
class ShowCategoryTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$withCounts = [
'showableAssets as assets_count',
'accessories as accessories_count',
'consumables as consumables_count',
'components as components_count',
'licenses as licenses_count',
];
$category = null;
if ($request->filled('id')) {
$category = Category::withCount($withCounts)->find($request->get('id'));
} elseif ($request->filled('name')) {
$category = Category::withCount($withCounts)->where('name', $request->get('name'))->first();
} else {
return Response::make(Response::error('Please provide an id or name'));
}
if (! $category) {
return Response::make(Response::error('Category not found'));
}
if (! Gate::allows('view', $category)) {
return Response::make(Response::error('Unauthorized'));
}
return Response::make(
Response::text('Category '.$category->name.' found')
)->withStructuredContent([
'id' => $category->id,
'name' => $category->name,
'category_type' => $category->category_type,
'assets_count' => $category->assets_count,
'accessories_count' => $category->accessories_count,
'consumables_count' => $category->consumables_count,
'components_count' => $category->components_count,
'licenses_count' => $category->licenses_count,
'notes' => $category->notes,
'created_at' => $category->created_at?->format('Y-m-d H:i:s'),
'updated_at' => $category->updated_at?->format('Y-m-d H:i:s'),
]);
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the category to look up'),
'name' => $schema->string()->description('Name of the category to look up'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric category ID'),
'name' => $schema->string()->description('Category name'),
'category_type' => $schema->string()->description('Category type: asset, accessory, consumable, component, or license'),
'assets_count' => $schema->number()->description('Number of assets in this category'),
'accessories_count' => $schema->number()->description('Number of accessories in this category'),
'consumables_count' => $schema->number()->description('Number of consumables in this category'),
'components_count' => $schema->number()->description('Number of components in this category'),
'licenses_count' => $schema->number()->description('Number of licenses in this category'),
'notes' => $schema->string()->description('Notes'),
'created_at' => $schema->string()->description('Creation timestamp'),
'updated_at' => $schema->string()->description('Last update timestamp'),
];
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Company;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('show_company')]
#[Title('Show Company Details')]
#[Description('Look up a single Snipe-IT company by numeric ID or name and return its full details')]
class ShowCompanyTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$company = null;
if ($request->filled('id')) {
$company = Company::withCount([
'assets as assets_count' => fn ($q) => $q->AssetsForShow(),
])->withCount('users as users_count')->find($request->get('id'));
} elseif ($request->filled('name')) {
$company = Company::withCount([
'assets as assets_count' => fn ($q) => $q->AssetsForShow(),
])->withCount('users as users_count')->where('name', $request->get('name'))->first();
} else {
return Response::make(Response::error('Please provide an id or name'));
}
if (! $company) {
return Response::make(Response::error('Company not found'));
}
if (! Gate::allows('view', $company)) {
return Response::make(Response::error('Unauthorized'));
}
return Response::make(
Response::text('Company '.$company->name.' found')
)->withStructuredContent([
'id' => $company->id,
'name' => $company->name,
'phone' => $company->phone,
'fax' => $company->fax,
'email' => $company->email,
'assets_count' => $company->assets_count,
'users_count' => $company->users_count,
'notes' => $company->notes,
'created_at' => $company->created_at?->format('Y-m-d H:i:s'),
'updated_at' => $company->updated_at?->format('Y-m-d H:i:s'),
]);
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the company to look up'),
'name' => $schema->string()->description('Name of the company to look up'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric company ID'),
'name' => $schema->string()->description('Company name'),
'phone' => $schema->string()->description('Company phone number'),
'fax' => $schema->string()->description('Company fax number'),
'email' => $schema->string()->description('Company email address'),
'assets_count' => $schema->number()->description('Number of assets belonging to this company'),
'users_count' => $schema->number()->description('Number of users belonging to this company'),
'notes' => $schema->string()->description('Notes'),
'created_at' => $schema->string()->description('Creation timestamp'),
'updated_at' => $schema->string()->description('Last update timestamp'),
];
}
}
+107
View File
@@ -0,0 +1,107 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Consumable;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('show_consumable')]
#[Title('Show Consumable Details')]
#[Description('Look up a single Snipe-IT consumable by numeric ID or name and return its full details')]
class ShowConsumableTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$consumable = null;
if ($request->filled('id')) {
$consumable = Consumable::with('company', 'category', 'manufacturer', 'supplier', 'location')->find($request->get('id'));
} elseif ($request->filled('name')) {
$consumable = Consumable::with('company', 'category', 'manufacturer', 'supplier', 'location')->where('name', $request->get('name'))->first();
} else {
return Response::make(Response::error('Either id or name is required'));
}
if (! $consumable) {
return Response::make(Response::error('Consumable not found'));
}
if (! Gate::allows('view', $consumable)) {
return Response::make(Response::error('Unauthorized'));
}
$usersCount = $consumable->users()->count();
return Response::make(
Response::text('Consumable '.$consumable->name.' found')
)->withStructuredContent([
'id' => $consumable->id,
'name' => $consumable->name,
'qty' => $consumable->qty,
'users_count' => $usersCount,
'min_amt' => $consumable->min_amt,
'category_id' => $consumable->category_id,
'category' => $consumable->category?->name,
'manufacturer_id' => $consumable->manufacturer_id,
'manufacturer' => $consumable->manufacturer?->name,
'company_id' => $consumable->company_id,
'company' => $consumable->company?->name,
'location_id' => $consumable->location_id,
'location' => $consumable->location?->name,
'purchase_cost' => $consumable->purchase_cost,
'purchase_date' => $consumable->purchase_date?->format('Y-m-d'),
'order_number' => $consumable->order_number,
'model_number' => $consumable->model_number,
'notes' => $consumable->notes,
'created_at' => $consumable->created_at?->format('Y-m-d H:i:s'),
'updated_at' => $consumable->updated_at?->format('Y-m-d H:i:s'),
]);
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the consumable to look up'),
'name' => $schema->string()->description('Name of the consumable to look up'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric consumable ID'),
'name' => $schema->string()->description('Consumable name'),
'qty' => $schema->number()->description('Total quantity in stock'),
'users_count' => $schema->number()->description('Number of units checked out'),
'min_amt' => $schema->number()->description('Minimum quantity alert threshold'),
'category_id' => $schema->number()->description('Category ID'),
'category' => $schema->string()->description('Category name'),
'manufacturer_id' => $schema->number()->description('Manufacturer ID'),
'manufacturer' => $schema->string()->description('Manufacturer name'),
'company_id' => $schema->number()->description('Company ID'),
'company' => $schema->string()->description('Company name'),
'location_id' => $schema->number()->description('Location ID'),
'location' => $schema->string()->description('Location name'),
'purchase_cost' => $schema->string()->description('Purchase cost'),
'purchase_date' => $schema->string()->description('Purchase date (YYYY-MM-DD)'),
'order_number' => $schema->string()->description('Order number'),
'model_number' => $schema->string()->description('Model number'),
'notes' => $schema->string()->description('Notes'),
'created_at' => $schema->string()->description('Creation timestamp'),
'updated_at' => $schema->string()->description('Last updated timestamp'),
];
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Depreciation;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('show_depreciation')]
#[Title('Show Depreciation Details')]
#[Description('Look up a single Snipe-IT depreciation schedule by numeric ID or name and return its full details')]
class ShowDepreciationTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$depreciation = $this->resolveDepreciation($request);
if ($depreciation === false) {
return Response::make(Response::error('Please provide an id or name'));
}
if (! $depreciation) {
return Response::make(Response::error('Depreciation not found'));
}
if (! Gate::allows('view', $depreciation)) {
return Response::make(Response::error('Unauthorized'));
}
$depreciation->loadCount('models as models_count');
return Response::make(
Response::text('Depreciation '.$depreciation->name.' found')
)->withStructuredContent([
'id' => $depreciation->id,
'name' => $depreciation->name,
'months' => $depreciation->months,
'models_count' => $depreciation->models_count,
'created_at' => $depreciation->created_at?->format('Y-m-d H:i:s'),
'updated_at' => $depreciation->updated_at?->format('Y-m-d H:i:s'),
]);
}
private function resolveDepreciation(Request $request): Depreciation|false|null
{
if ($request->filled('id')) {
return Depreciation::find($request->get('id'));
}
if ($request->filled('name')) {
return Depreciation::where('name', $request->get('name'))->first();
}
return false;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the depreciation to look up'),
'name' => $schema->string()->description('Name of the depreciation to look up'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric depreciation ID'),
'name' => $schema->string()->description('Depreciation name'),
'months' => $schema->number()->description('Depreciation period in months'),
'models_count' => $schema->number()->description('Number of asset models using this depreciation'),
'created_at' => $schema->string()->description('Creation timestamp'),
'updated_at' => $schema->string()->description('Last update timestamp'),
];
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Group;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('show_group')]
#[Title('Show Group')]
#[Description('Look up a single Snipe-IT permission group by numeric ID or name')]
class ShowGroupTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('superadmin')) {
return Response::make(Response::error('Unauthorized'));
}
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
if ($request->filled('id')) {
$group = Group::withCount('users as users_count')->find($request->get('id'));
} elseif ($request->filled('name')) {
$group = Group::withCount('users as users_count')
->where('name', $request->get('name'))
->first();
} else {
return Response::make(Response::error('Please provide an id or name'));
}
if (! $group) {
return Response::make(Response::error('Group not found'));
}
return Response::make(
Response::text('Group '.$group->name.' found')
)->withStructuredContent([
'id' => $group->id,
'name' => $group->name,
'notes' => $group->notes,
'permissions' => $group->decodePermissions(),
'users_count' => $group->users_count,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric group ID'),
'name' => $schema->string()->description('Group name to look up'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric group ID')->required(),
'name' => $schema->string()->description('Group name')->required(),
'notes' => $schema->string()->description('Notes about the group'),
'permissions' => $schema->object()->description('Decoded permissions array'),
'users_count' => $schema->number()->description('Number of users in this group'),
];
}
}
+109
View File
@@ -0,0 +1,109 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Location;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('show_location')]
#[Title('Show Location Details')]
#[Description('Look up a single Snipe-IT location by numeric ID or name and return its full details')]
class ShowLocationTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$location = $this->resolveLocation($request);
if ($location === false) {
return Response::make(Response::error('Please provide an id or name'));
}
if (! $location) {
return Response::make(Response::error('Location not found'));
}
if (! Gate::allows('view', $location)) {
return Response::make(Response::error('Unauthorized'));
}
$location->loadCount('assets as assets_count', 'users as users_count', 'children as children_count');
return Response::make(
Response::text('Location '.$location->name.' found')
)->withStructuredContent([
'id' => $location->id,
'name' => $location->name,
'address' => $location->address,
'address2' => $location->address2,
'city' => $location->city,
'state' => $location->state,
'country' => $location->country,
'zip' => $location->zip,
'phone' => $location->phone,
'fax' => $location->fax,
'parent_id' => $location->parent_id,
'parent' => $location->parent?->name,
'assets_count' => $location->assets_count,
'users_count' => $location->users_count,
'children_count' => $location->children_count,
'created_at' => $location->created_at?->format('Y-m-d H:i:s'),
'updated_at' => $location->updated_at?->format('Y-m-d H:i:s'),
]);
}
private function resolveLocation(Request $request): Location|false|null
{
if ($request->filled('id')) {
return Location::with('parent')->find($request->get('id'));
}
if ($request->filled('name')) {
return Location::with('parent')->where('name', $request->get('name'))->first();
}
return false;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the location to look up'),
'name' => $schema->string()->description('Name of the location to look up'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric location ID'),
'name' => $schema->string()->description('Location name'),
'address' => $schema->string()->description('Street address'),
'address2' => $schema->string()->description('Address line 2'),
'city' => $schema->string()->description('City'),
'state' => $schema->string()->description('State'),
'country' => $schema->string()->description('Country'),
'zip' => $schema->string()->description('Zip code'),
'phone' => $schema->string()->description('Phone number'),
'fax' => $schema->string()->description('Fax number'),
'parent_id' => $schema->number()->description('Parent location ID'),
'parent' => $schema->string()->description('Parent location name'),
'assets_count' => $schema->number()->description('Number of assets at this location'),
'users_count' => $schema->number()->description('Number of users at this location'),
'children_count' => $schema->number()->description('Number of child locations'),
'created_at' => $schema->string()->description('Creation timestamp'),
'updated_at' => $schema->string()->description('Last update timestamp'),
];
}
}
+97
View File
@@ -0,0 +1,97 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Manufacturer;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('show_manufacturer')]
#[Title('Show Manufacturer')]
#[Description('Show details of a Snipe-IT manufacturer identified by numeric ID or name')]
class ShowManufacturerTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$manufacturer = $this->resolveManufacturer($request);
if (! $manufacturer) {
if (! $request->filled('id') && ! $request->filled('name')) {
return Response::make(Response::error('Please provide an id or name'));
}
return Response::make(Response::error('Manufacturer not found'));
}
if (! Gate::allows('view', $manufacturer)) {
return Response::make(Response::error('Unauthorized'));
}
$manufacturer->loadCount('assets as assets_count');
return Response::make(
Response::text('Manufacturer: '.$manufacturer->name)
)->withStructuredContent([
'id' => $manufacturer->id,
'name' => $manufacturer->name,
'url' => $manufacturer->url,
'support_url' => $manufacturer->support_url,
'support_email' => $manufacturer->support_email,
'support_phone' => $manufacturer->support_phone,
'warranty_lookup_url' => $manufacturer->warranty_lookup_url,
'assets_count' => $manufacturer->assets_count,
'notes' => $manufacturer->notes,
'created_at' => $manufacturer->created_at?->toISOString(),
'updated_at' => $manufacturer->updated_at?->toISOString(),
]);
}
private function resolveManufacturer(Request $request): ?Manufacturer
{
if ($request->filled('id')) {
return Manufacturer::find($request->get('id'));
}
if ($request->filled('name')) {
return Manufacturer::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the manufacturer to show'),
'name' => $schema->string()->description('Name of the manufacturer to show'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the manufacturer'),
'name' => $schema->string()->description('Manufacturer name')->required(),
'url' => $schema->string()->description('Manufacturer website URL'),
'support_url' => $schema->string()->description('Support website URL'),
'support_email' => $schema->string()->description('Support email address'),
'support_phone' => $schema->string()->description('Support phone number'),
'warranty_lookup_url' => $schema->string()->description('Warranty lookup URL'),
'assets_count' => $schema->number()->description('Number of assets from this manufacturer'),
'notes' => $schema->string()->description('Notes'),
'created_at' => $schema->string()->description('Creation timestamp'),
'updated_at' => $schema->string()->description('Last update timestamp'),
];
}
}
+101
View File
@@ -0,0 +1,101 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Statuslabel;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('show_status_label')]
#[Title('Show Status Label')]
#[Description('Show details of a Snipe-IT status label identified by numeric ID or name')]
class ShowStatusLabelTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$label = $this->resolveStatusLabel($request);
if (! $label) {
if (! $request->filled('id') && ! $request->filled('name')) {
return Response::make(Response::error('Please provide an id or name'));
}
return Response::make(Response::error('Status label not found'));
}
if (! Gate::allows('view', $label)) {
return Response::make(Response::error('Unauthorized'));
}
$label->loadCount('assets as assets_count');
return Response::make(
Response::text('Status label: '.$label->name)
)->withStructuredContent([
'id' => $label->id,
'name' => $label->name,
'type' => $label->getStatuslabelType(),
'color' => $label->color,
'deployable' => $label->deployable,
'pending' => $label->pending,
'archived' => $label->archived,
'assets_count' => $label->assets_count,
'default_label' => $label->default_label,
'show_in_nav' => $label->show_in_nav,
'notes' => $label->notes,
'created_at' => $label->created_at?->toISOString(),
'updated_at' => $label->updated_at?->toISOString(),
]);
}
private function resolveStatusLabel(Request $request): ?Statuslabel
{
if ($request->filled('id')) {
return Statuslabel::find($request->get('id'));
}
if ($request->filled('name')) {
return Statuslabel::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the status label to show'),
'name' => $schema->string()->description('Name of the status label to show'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the status label'),
'name' => $schema->string()->description('Status label name')->required(),
'type' => $schema->string()->description('Status label type (deployable, pending, archived, undeployable)'),
'color' => $schema->string()->description('Display color'),
'deployable' => $schema->boolean()->description('Whether status is deployable'),
'pending' => $schema->boolean()->description('Whether status is pending'),
'archived' => $schema->boolean()->description('Whether status is archived'),
'assets_count' => $schema->number()->description('Number of assets with this status'),
'default_label' => $schema->boolean()->description('Whether this is the default label'),
'show_in_nav' => $schema->boolean()->description('Whether to show in navigation'),
'notes' => $schema->string()->description('Notes'),
'created_at' => $schema->string()->description('Creation timestamp'),
'updated_at' => $schema->string()->description('Last update timestamp'),
];
}
}
+111
View File
@@ -0,0 +1,111 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Supplier;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('show_supplier')]
#[Title('Show Supplier')]
#[Description('Show details of a Snipe-IT supplier identified by numeric ID or name')]
class ShowSupplierTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
]);
$supplier = $this->resolveSupplier($request);
if (! $supplier) {
if (! $request->filled('id') && ! $request->filled('name')) {
return Response::make(Response::error('Please provide an id or name'));
}
return Response::make(Response::error('Supplier not found'));
}
if (! Gate::allows('view', $supplier)) {
return Response::make(Response::error('Unauthorized'));
}
$supplier->loadCount('assets as assets_count', 'licenses as licenses_count');
return Response::make(
Response::text('Supplier: '.$supplier->name)
)->withStructuredContent([
'id' => $supplier->id,
'name' => $supplier->name,
'address' => $supplier->address,
'address2' => $supplier->address2,
'city' => $supplier->city,
'state' => $supplier->state,
'country' => $supplier->country,
'zip' => $supplier->zip,
'phone' => $supplier->phone,
'fax' => $supplier->fax,
'email' => $supplier->email,
'url' => $supplier->url,
'contact' => $supplier->contact,
'notes' => $supplier->notes,
'assets_count' => $supplier->assets_count,
'licenses_count' => $supplier->licenses_count,
'created_at' => $supplier->created_at?->toISOString(),
'updated_at' => $supplier->updated_at?->toISOString(),
]);
}
private function resolveSupplier(Request $request): ?Supplier
{
if ($request->filled('id')) {
return Supplier::find($request->get('id'));
}
if ($request->filled('name')) {
return Supplier::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the supplier to show'),
'name' => $schema->string()->description('Name of the supplier to show'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID of the supplier'),
'name' => $schema->string()->description('Supplier name')->required(),
'address' => $schema->string()->description('Address line 1'),
'address2' => $schema->string()->description('Address line 2'),
'city' => $schema->string()->description('City'),
'state' => $schema->string()->description('State'),
'country' => $schema->string()->description('Country'),
'zip' => $schema->string()->description('Postal code'),
'phone' => $schema->string()->description('Phone number'),
'fax' => $schema->string()->description('Fax number'),
'email' => $schema->string()->description('Email address'),
'url' => $schema->string()->description('Website URL'),
'contact' => $schema->string()->description('Contact name'),
'notes' => $schema->string()->description('Notes'),
'assets_count' => $schema->number()->description('Number of assets from this supplier'),
'licenses_count' => $schema->number()->description('Number of licenses from this supplier'),
'created_at' => $schema->string()->description('Creation timestamp'),
'updated_at' => $schema->string()->description('Last update timestamp'),
];
}
}
+111
View File
@@ -0,0 +1,111 @@
<?php
namespace App\Mcp\Tools;
use App\Models\AssetModel;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('update_asset_model')]
#[Title('Update Asset Model')]
#[Description('Update fields on a Snipe-IT asset model identified by numeric ID or name')]
class UpdateAssetModelTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
'new_name' => 'nullable|string|max:255',
'category_id' => 'nullable|integer|exists:categories,id',
'manufacturer_id' => 'nullable|integer|exists:manufacturers,id',
'depreciation_id' => 'nullable|integer|exists:depreciations,id',
'model_number' => 'nullable|string|max:255',
'eol' => 'nullable|integer|min:0|max:240',
'min_amt' => 'nullable|integer|min:0',
'notes' => 'nullable|string',
'requestable' => 'nullable|boolean',
'require_serial' => 'nullable|boolean',
]);
$model = $this->resolveModel($request);
if (! $model) {
return Response::make(Response::error('Asset model not found'));
}
if (! Gate::allows('update', $model)) {
return Response::make(Response::error('Unauthorized'));
}
if ($request->filled('new_name')) {
$model->name = $request->get('new_name');
}
foreach (['category_id', 'manufacturer_id', 'depreciation_id', 'model_number', 'eol', 'min_amt', 'notes', 'requestable', 'require_serial'] as $field) {
if ($request->filled($field)) {
$model->{$field} = $request->get($field);
}
}
if ($model->save()) {
return Response::make(
Response::text('Asset model '.$model->name.' updated successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Asset model updated successfully',
'id' => $model->id,
'name' => $model->name,
]);
}
return Response::make(Response::error('Update failed: '.$model->getErrors()->first()));
}
private function resolveModel(Request $request): ?AssetModel
{
if ($request->filled('id')) {
return AssetModel::find($request->get('id'));
}
if ($request->filled('name')) {
return AssetModel::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID to identify the asset model'),
'name' => $schema->string()->description('Name to identify the asset model'),
'new_name' => $schema->string()->description('New name (renames the asset model)'),
'category_id' => $schema->number()->description('Category ID'),
'manufacturer_id' => $schema->number()->description('Manufacturer ID'),
'depreciation_id' => $schema->number()->description('Depreciation schedule ID'),
'model_number' => $schema->string()->description('Model number'),
'eol' => $schema->number()->description('End of life in months (0-240)'),
'min_amt' => $schema->number()->description('Minimum quantity alert threshold'),
'notes' => $schema->string()->description('Notes'),
'requestable' => $schema->boolean()->description('Whether the model can be requested'),
'require_serial' => $schema->boolean()->description('Whether serial numbers are required'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the update succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the asset model'),
'name' => $schema->string()->description('Name of the asset model'),
];
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Category;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('update_category')]
#[Title('Update Category')]
#[Description('Update fields on a Snipe-IT category identified by numeric ID or name')]
class UpdateCategoryTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
'new_name' => 'nullable|string|max:255',
'category_type' => 'nullable|string|in:asset,accessory,consumable,component,license',
'notes' => 'nullable|string',
'checkin_email' => 'nullable|boolean',
'require_acceptance' => 'nullable|boolean',
'use_default_eula' => 'nullable|boolean',
]);
$category = $this->resolveCategory($request);
if (! $category) {
return Response::make(Response::error('Category not found'));
}
if (! Gate::allows('update', $category)) {
return Response::make(Response::error('Unauthorized'));
}
if ($request->filled('new_name')) {
$category->name = $request->get('new_name');
}
foreach (['category_type', 'notes', 'checkin_email', 'require_acceptance', 'use_default_eula'] as $field) {
if ($request->filled($field)) {
$category->{$field} = $request->get($field);
}
}
if ($category->save()) {
return Response::make(
Response::text('Category '.$category->name.' updated successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Category updated successfully',
'id' => $category->id,
'name' => $category->name,
'category_type' => $category->category_type,
]);
}
return Response::make(Response::error('Update failed: '.$category->getErrors()->first()));
}
private function resolveCategory(Request $request): ?Category
{
if ($request->filled('id')) {
return Category::find($request->get('id'));
}
if ($request->filled('name')) {
return Category::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID to identify the category'),
'name' => $schema->string()->description('Name to identify the category'),
'new_name' => $schema->string()->description('New name (renames the category)'),
'category_type' => $schema->string()->description('Category type: asset, accessory, consumable, component, or license'),
'checkin_email' => $schema->boolean()->description('Send checkin email when items are checked in'),
'require_acceptance' => $schema->boolean()->description('Require user acceptance when checking out'),
'use_default_eula' => $schema->boolean()->description('Use the default EULA'),
'notes' => $schema->string()->description('Notes'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the update succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the category'),
'name' => $schema->string()->description('Name of the category'),
'category_type' => $schema->string()->description('Type of the category'),
];
}
}
+101
View File
@@ -0,0 +1,101 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Company;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('update_company')]
#[Title('Update Company')]
#[Description('Update fields on a Snipe-IT company identified by numeric ID or name')]
class UpdateCompanyTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
'new_name' => 'nullable|string|max:255',
'phone' => 'nullable|string',
'fax' => 'nullable|string',
'email' => 'nullable|string',
'notes' => 'nullable|string',
]);
$company = $this->resolveCompany($request);
if (! $company) {
return Response::make(Response::error('Company not found'));
}
if (! Gate::allows('update', $company)) {
return Response::make(Response::error('Unauthorized'));
}
if ($request->filled('new_name')) {
$company->name = $request->get('new_name');
}
foreach (['phone', 'fax', 'email', 'notes'] as $field) {
if ($request->filled($field)) {
$company->{$field} = $request->get($field);
}
}
if ($company->save()) {
return Response::make(
Response::text('Company '.$company->name.' updated successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Company updated successfully',
'id' => $company->id,
'name' => $company->name,
]);
}
return Response::make(Response::error('Update failed: '.$company->getErrors()->first()));
}
private function resolveCompany(Request $request): ?Company
{
if ($request->filled('id')) {
return Company::find($request->get('id'));
}
if ($request->filled('name')) {
return Company::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID to identify the company'),
'name' => $schema->string()->description('Name to identify the company'),
'new_name' => $schema->string()->description('New name (renames the company)'),
'phone' => $schema->string()->description('Company phone number'),
'fax' => $schema->string()->description('Company fax number'),
'email' => $schema->string()->description('Company email address'),
'notes' => $schema->string()->description('Notes'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the update succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the company'),
'name' => $schema->string()->description('Name of the company'),
];
}
}
+120
View File
@@ -0,0 +1,120 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Consumable;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('update_consumable')]
#[Title('Update Consumable')]
#[Description('Update fields on a Snipe-IT consumable identified by numeric ID or name')]
class UpdateConsumableTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
'new_name' => 'nullable|string|max:255',
'qty' => 'nullable|integer|min:0',
'category_id' => 'nullable|integer|exists:categories,id',
'company_id' => 'nullable|integer',
'location_id' => 'nullable|integer|exists:locations,id',
'manufacturer_id' => 'nullable|integer|exists:manufacturers,id',
'supplier_id' => 'nullable|integer',
'purchase_cost' => 'nullable|numeric|min:0',
'purchase_date' => 'nullable|date_format:Y-m-d',
'min_amt' => 'nullable|integer|min:0',
'notes' => 'nullable|string',
]);
$consumable = $this->resolveConsumable($request);
if (! $consumable) {
return Response::make(Response::error('Consumable not found'));
}
if (! Gate::allows('update', $consumable)) {
return Response::make(Response::error('Unauthorized'));
}
$updatable = [
'qty', 'category_id', 'company_id', 'location_id', 'manufacturer_id',
'supplier_id', 'purchase_cost', 'purchase_date', 'min_amt', 'notes',
];
foreach ($updatable as $field) {
if ($request->filled($field)) {
$consumable->{$field} = $request->get($field);
}
}
if ($request->filled('new_name')) {
$consumable->name = $request->get('new_name');
}
if ($consumable->save()) {
return Response::make(
Response::text('Consumable '.$consumable->name.' updated successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Consumable updated successfully',
'id' => $consumable->id,
'name' => $consumable->name,
'qty' => $consumable->qty,
]);
}
return Response::make(Response::error('Update failed: '.$consumable->getErrors()->first()));
}
private function resolveConsumable(Request $request): ?Consumable
{
if ($request->filled('id')) {
return Consumable::find($request->get('id'));
}
if ($request->filled('name')) {
return Consumable::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID to identify the consumable'),
'name' => $schema->string()->description('Name to identify the consumable'),
'new_name' => $schema->string()->description('New name (renames the consumable)'),
'qty' => $schema->number()->description('Total quantity in stock'),
'category_id' => $schema->number()->description('Category ID'),
'company_id' => $schema->number()->description('Company ID'),
'location_id' => $schema->number()->description('Location ID'),
'manufacturer_id' => $schema->number()->description('Manufacturer ID'),
'supplier_id' => $schema->number()->description('Supplier ID'),
'purchase_cost' => $schema->number()->description('Purchase cost per unit'),
'purchase_date' => $schema->string()->description('Purchase date (YYYY-MM-DD)'),
'min_amt' => $schema->number()->description('Minimum quantity alert threshold'),
'notes' => $schema->string()->description('Notes'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the update succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the consumable'),
'name' => $schema->string()->description('Name of the consumable'),
'qty' => $schema->number()->description('Total quantity'),
];
}
}
+95
View File
@@ -0,0 +1,95 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Depreciation;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('update_depreciation')]
#[Title('Update Depreciation')]
#[Description('Update fields on a Snipe-IT depreciation schedule identified by numeric ID or name')]
class UpdateDepreciationTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
'new_name' => 'nullable|string|max:255',
'months' => 'nullable|integer|min:1|max:3600',
]);
$dep = $this->resolveDepreciation($request);
if (! $dep) {
return Response::make(Response::error('Depreciation not found'));
}
if (! Gate::allows('update', $dep)) {
return Response::make(Response::error('Unauthorized'));
}
if ($request->filled('new_name')) {
$dep->name = $request->get('new_name');
}
if ($request->filled('months')) {
$dep->months = $request->get('months');
}
if ($dep->save()) {
return Response::make(
Response::text('Depreciation '.$dep->name.' updated successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Depreciation updated successfully',
'id' => $dep->id,
'name' => $dep->name,
'months' => $dep->months,
]);
}
return Response::make(Response::error('Update failed: '.$dep->getErrors()->first()));
}
private function resolveDepreciation(Request $request): ?Depreciation
{
if ($request->filled('id')) {
return Depreciation::find($request->get('id'));
}
if ($request->filled('name')) {
return Depreciation::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID to identify the depreciation'),
'name' => $schema->string()->description('Name to identify the depreciation'),
'new_name' => $schema->string()->description('New name (renames the depreciation)'),
'months' => $schema->number()->description('Depreciation period in months (1-3600)'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the update succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the depreciation'),
'name' => $schema->string()->description('Name of the depreciation'),
'months' => $schema->number()->description('Depreciation period in months'),
];
}
}
+92
View File
@@ -0,0 +1,92 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Group;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('update_group')]
#[Title('Update Group')]
#[Description('Update an existing Snipe-IT permission group by ID or name')]
class UpdateGroupTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
if (! Gate::allows('superadmin')) {
return Response::make(Response::error('Unauthorized'));
}
try {
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
'new_name' => 'nullable|string|max:255',
'notes' => 'nullable|string',
]);
} catch (ValidationException $e) {
return Response::make(Response::error($e->validator->errors()->first()));
}
if ($request->filled('id')) {
$group = Group::find($request->get('id'));
} elseif ($request->filled('name')) {
$group = Group::where('name', $request->get('name'))->first();
} else {
return Response::make(Response::error('Please provide an id or name'));
}
if (! $group) {
return Response::make(Response::error('Group not found'));
}
if ($request->filled('new_name')) {
$group->name = $request->get('new_name');
}
if ($request->filled('notes')) {
$group->notes = $request->get('notes');
}
if ($group->save()) {
return Response::make(
Response::text('Group '.$group->name.' updated successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Group updated successfully',
'id' => $group->id,
'name' => $group->name,
]);
}
return Response::make(Response::error('Update failed: '.$group->getErrors()->first()));
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric group ID to update'),
'name' => $schema->string()->description('Group name to look up for updating'),
'new_name' => $schema->string()->description('New name to rename the group to'),
'notes' => $schema->string()->description('Updated notes for the group'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the update succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the updated group'),
'name' => $schema->string()->description('Name of the updated group'),
];
}
}
+113
View File
@@ -0,0 +1,113 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Location;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('update_location')]
#[Title('Update Location')]
#[Description('Update fields on a Snipe-IT location identified by numeric ID or name')]
class UpdateLocationTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
'new_name' => 'nullable|string|max:255',
'address' => 'nullable|string',
'city' => 'nullable|string',
'state' => 'nullable|string',
'country' => 'nullable|string',
'zip' => 'nullable|string',
'phone' => 'nullable|string',
'fax' => 'nullable|string',
'currency' => 'nullable|string',
'parent_id' => 'nullable|integer|exists:locations,id',
'manager_id' => 'nullable|integer|exists:users,id',
]);
$location = $this->resolveLocation($request);
if (! $location) {
return Response::make(Response::error('Location not found'));
}
if (! Gate::allows('update', $location)) {
return Response::make(Response::error('Unauthorized'));
}
if ($request->filled('new_name')) {
$location->name = $request->get('new_name');
}
foreach (['address', 'city', 'state', 'country', 'zip', 'phone', 'fax', 'currency', 'parent_id', 'manager_id'] as $field) {
if ($request->filled($field)) {
$location->{$field} = $request->get($field);
}
}
if ($location->save()) {
return Response::make(
Response::text('Location '.$location->name.' updated successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Location updated successfully',
'id' => $location->id,
'name' => $location->name,
]);
}
return Response::make(Response::error('Update failed: '.$location->getErrors()->first()));
}
private function resolveLocation(Request $request): ?Location
{
if ($request->filled('id')) {
return Location::find($request->get('id'));
}
if ($request->filled('name')) {
return Location::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID to identify the location'),
'name' => $schema->string()->description('Name to identify the location'),
'new_name' => $schema->string()->description('New name (renames the location)'),
'address' => $schema->string()->description('Street address'),
'city' => $schema->string()->description('City'),
'state' => $schema->string()->description('State'),
'country' => $schema->string()->description('Country'),
'zip' => $schema->string()->description('Zip code'),
'phone' => $schema->string()->description('Phone number'),
'fax' => $schema->string()->description('Fax number'),
'currency' => $schema->string()->description('Currency code'),
'parent_id' => $schema->number()->description('Parent location ID'),
'manager_id' => $schema->number()->description('Manager user ID'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the update succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the location'),
'name' => $schema->string()->description('Name of the location'),
];
}
}
+107
View File
@@ -0,0 +1,107 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Manufacturer;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('update_manufacturer')]
#[Title('Update Manufacturer')]
#[Description('Update fields on a Snipe-IT manufacturer identified by numeric ID or name')]
class UpdateManufacturerTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
'new_name' => 'nullable|string|max:255',
'url' => 'nullable|string|max:255',
'support_url' => 'nullable|string|max:255',
'support_email' => 'nullable|email|max:191',
'support_phone' => 'nullable|string|max:191',
'warranty_lookup_url' => 'nullable|string|max:255',
'notes' => 'nullable|string',
]);
$manufacturer = $this->resolveManufacturer($request);
if (! $manufacturer) {
return Response::make(Response::error('Manufacturer not found'));
}
if (! Gate::allows('update', $manufacturer)) {
return Response::make(Response::error('Unauthorized'));
}
if ($request->filled('new_name')) {
$manufacturer->name = $request->get('new_name');
}
$updatable = ['url', 'support_url', 'support_email', 'support_phone', 'warranty_lookup_url', 'notes'];
foreach ($updatable as $field) {
if ($request->filled($field)) {
$manufacturer->{$field} = $request->get($field);
}
}
if ($manufacturer->save()) {
return Response::make(
Response::text('Manufacturer '.$manufacturer->name.' updated successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Manufacturer updated successfully',
'id' => $manufacturer->id,
'name' => $manufacturer->name,
]);
}
return Response::make(Response::error('Update failed: '.$manufacturer->getErrors()->first()));
}
private function resolveManufacturer(Request $request): ?Manufacturer
{
if ($request->filled('id')) {
return Manufacturer::find($request->get('id'));
}
if ($request->filled('name')) {
return Manufacturer::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID to identify the manufacturer'),
'name' => $schema->string()->description('Name to identify the manufacturer'),
'new_name' => $schema->string()->description('New name (renames the manufacturer)'),
'url' => $schema->string()->description('Manufacturer website URL'),
'support_url' => $schema->string()->description('Support website URL'),
'support_email' => $schema->string()->description('Support email address'),
'support_phone' => $schema->string()->description('Support phone number'),
'warranty_lookup_url' => $schema->string()->description('Warranty lookup URL'),
'notes' => $schema->string()->description('Notes'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the update succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the manufacturer'),
'name' => $schema->string()->description('Name of the manufacturer'),
];
}
}
+122
View File
@@ -0,0 +1,122 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Statuslabel;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('update_status_label')]
#[Title('Update Status Label')]
#[Description('Update fields on a Snipe-IT status label identified by numeric ID or name')]
class UpdateStatusLabelTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
'new_name' => 'nullable|string|max:255',
'type' => 'nullable|string|in:deployable,pending,archived,undeployable',
'color' => 'nullable|string',
'notes' => 'nullable|string',
'default_label' => 'nullable|boolean',
'show_in_nav' => 'nullable|boolean',
]);
$label = $this->resolveStatusLabel($request);
if (! $label) {
return Response::make(Response::error('Status label not found'));
}
if (! Gate::allows('update', $label)) {
return Response::make(Response::error('Unauthorized'));
}
if ($request->filled('new_name')) {
$label->name = $request->get('new_name');
}
if ($request->filled('type')) {
$statusType = Statuslabel::getStatuslabelTypesForDB($request->get('type'));
$label->deployable = $statusType['deployable'];
$label->pending = $statusType['pending'];
$label->archived = $statusType['archived'];
}
if ($request->filled('color')) {
$label->color = $request->get('color');
}
if ($request->filled('notes')) {
$label->notes = $request->get('notes');
}
if ($request->has('default_label')) {
$label->default_label = $request->get('default_label');
}
if ($request->has('show_in_nav')) {
$label->show_in_nav = $request->get('show_in_nav');
}
if ($label->save()) {
return Response::make(
Response::text('Status label '.$label->name.' updated successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Status label updated successfully',
'id' => $label->id,
'name' => $label->name,
'type' => $label->getStatuslabelType(),
]);
}
return Response::make(Response::error('Update failed: '.$label->getErrors()->first()));
}
private function resolveStatusLabel(Request $request): ?Statuslabel
{
if ($request->filled('id')) {
return Statuslabel::find($request->get('id'));
}
if ($request->filled('name')) {
return Statuslabel::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID to identify the status label'),
'name' => $schema->string()->description('Name to identify the status label'),
'new_name' => $schema->string()->description('New name (renames the status label)'),
'type' => $schema->string()->description('New type: deployable, pending, archived, or undeployable'),
'color' => $schema->string()->description('Display color in #RRGGBB format'),
'notes' => $schema->string()->description('Notes'),
'default_label' => $schema->boolean()->description('Whether this is the default label'),
'show_in_nav' => $schema->boolean()->description('Whether to show in navigation'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the update succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the status label'),
'name' => $schema->string()->description('Name of the status label'),
'type' => $schema->string()->description('Type of the status label'),
];
}
}
+119
View File
@@ -0,0 +1,119 @@
<?php
namespace App\Mcp\Tools;
use App\Models\Supplier;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Gate;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;
#[Name('update_supplier')]
#[Title('Update Supplier')]
#[Description('Update fields on a Snipe-IT supplier identified by numeric ID or name')]
class UpdateSupplierTool extends Tool
{
public function handle(Request $request): ResponseFactory
{
$request->validate([
'id' => 'nullable|integer',
'name' => 'nullable|string|max:255',
'new_name' => 'nullable|string|max:255',
'address' => 'nullable|string',
'address2' => 'nullable|string',
'city' => 'nullable|string',
'state' => 'nullable|string',
'country' => 'nullable|string',
'zip' => 'nullable|string',
'phone' => 'nullable|string',
'fax' => 'nullable|string',
'email' => 'nullable|email',
'url' => 'nullable|string',
'contact' => 'nullable|string',
'notes' => 'nullable|string',
]);
$supplier = $this->resolveSupplier($request);
if (! $supplier) {
return Response::make(Response::error('Supplier not found'));
}
if (! Gate::allows('update', $supplier)) {
return Response::make(Response::error('Unauthorized'));
}
if ($request->filled('new_name')) {
$supplier->name = $request->get('new_name');
}
$updatable = ['address', 'address2', 'city', 'state', 'country', 'zip', 'phone', 'fax', 'email', 'url', 'contact', 'notes'];
foreach ($updatable as $field) {
if ($request->filled($field)) {
$supplier->{$field} = $request->get($field);
}
}
if ($supplier->save()) {
return Response::make(
Response::text('Supplier '.$supplier->name.' updated successfully')
)->withStructuredContent([
'success' => true,
'message' => 'Supplier updated successfully',
'id' => $supplier->id,
'name' => $supplier->name,
]);
}
return Response::make(Response::error('Update failed: '.$supplier->getErrors()->first()));
}
private function resolveSupplier(Request $request): ?Supplier
{
if ($request->filled('id')) {
return Supplier::find($request->get('id'));
}
if ($request->filled('name')) {
return Supplier::where('name', $request->get('name'))->first();
}
return null;
}
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->number()->description('Numeric ID to identify the supplier'),
'name' => $schema->string()->description('Name to identify the supplier'),
'new_name' => $schema->string()->description('New name (renames the supplier)'),
'address' => $schema->string()->description('Address line 1'),
'address2' => $schema->string()->description('Address line 2'),
'city' => $schema->string()->description('City'),
'state' => $schema->string()->description('State'),
'country' => $schema->string()->description('Country'),
'zip' => $schema->string()->description('Postal code'),
'phone' => $schema->string()->description('Phone number'),
'fax' => $schema->string()->description('Fax number'),
'email' => $schema->string()->description('Email address'),
'url' => $schema->string()->description('Website URL'),
'contact' => $schema->string()->description('Contact name'),
'notes' => $schema->string()->description('Notes'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'success' => $schema->boolean()->description('True if the update succeeded'),
'message' => $schema->string()->description('Human-readable result message')->required(),
'id' => $schema->number()->description('Numeric ID of the supplier'),
'name' => $schema->string()->description('Name of the supplier'),
];
}
}
+105 -5
View File
@@ -180,16 +180,26 @@ class UserFactory extends Factory
return $this->appendPermission(['assets.view.requestable' => '1']);
}
public function deleteAssetModels()
{
return $this->appendPermission(['models.delete' => '1']);
}
public function viewAssetModels()
{
return $this->appendPermission(['models.view' => '1']);
}
public function createAssetModels()
{
return $this->appendPermission(['models.create' => '1']);
}
public function editAssetModels()
{
return $this->appendPermission(['models.edit' => '1']);
}
public function deleteAssetModels()
{
return $this->appendPermission(['models.delete' => '1']);
}
public function viewAccessories()
{
return $this->appendPermission(['accessories.view' => '1']);
@@ -370,11 +380,41 @@ class UserFactory extends Factory
return $this->appendPermission(['users.delete' => '1']);
}
public function viewCategories()
{
return $this->appendPermission(['categories.view' => '1']);
}
public function createCategories()
{
return $this->appendPermission(['categories.create' => '1']);
}
public function editCategories()
{
return $this->appendPermission(['categories.edit' => '1']);
}
public function deleteCategories()
{
return $this->appendPermission(['categories.delete' => '1']);
}
public function viewLocations()
{
return $this->appendPermission(['locations.view' => '1']);
}
public function createLocations()
{
return $this->appendPermission(['locations.create' => '1']);
}
public function editLocations()
{
return $this->appendPermission(['locations.edit' => '1']);
}
public function deleteLocations()
{
return $this->appendPermission(['locations.delete' => '1']);
@@ -415,11 +455,41 @@ class UserFactory extends Factory
return $this->appendPermission(['customfields.delete' => '1']);
}
public function viewDepreciations()
{
return $this->appendPermission(['depreciations.view' => '1']);
}
public function createDepreciations()
{
return $this->appendPermission(['depreciations.create' => '1']);
}
public function editDepreciations()
{
return $this->appendPermission(['depreciations.edit' => '1']);
}
public function deleteDepreciations()
{
return $this->appendPermission(['depreciations.delete' => '1']);
}
public function viewManufacturers()
{
return $this->appendPermission(['manufacturers.view' => '1']);
}
public function createManufacturers()
{
return $this->appendPermission(['manufacturers.create' => '1']);
}
public function editManufacturers()
{
return $this->appendPermission(['manufacturers.edit' => '1']);
}
public function deleteManufacturers()
{
return $this->appendPermission(['manufacturers.delete' => '1']);
@@ -435,11 +505,41 @@ class UserFactory extends Factory
return $this->appendPermission(['kits.view' => '1']);
}
public function viewStatusLabels()
{
return $this->appendPermission(['statuslabels.view' => '1']);
}
public function createStatusLabels()
{
return $this->appendPermission(['statuslabels.create' => '1']);
}
public function editStatusLabels()
{
return $this->appendPermission(['statuslabels.edit' => '1']);
}
public function deleteStatusLabels()
{
return $this->appendPermission(['statuslabels.delete' => '1']);
}
public function viewSuppliers()
{
return $this->appendPermission(['suppliers.view' => '1']);
}
public function createSuppliers()
{
return $this->appendPermission(['suppliers.create' => '1']);
}
public function editSuppliers()
{
return $this->appendPermission(['suppliers.edit' => '1']);
}
public function deleteSuppliers()
{
return $this->appendPermission(['suppliers.delete' => '1']);
@@ -3,7 +3,6 @@
namespace Tests\Feature\Mcp;
use App\Mcp\Tools\CreateAssetModelTool;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\User;
use Laravel\Mcp\Request;
@@ -3,7 +3,6 @@
namespace Tests\Feature\Mcp;
use App\Mcp\Tools\CreateAssetTool;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Statuslabel;
use App\Models\User;
@@ -3,7 +3,6 @@
namespace Tests\Feature\Mcp;
use App\Mcp\Tools\CreateDepreciationTool;
use App\Models\Depreciation;
use App\Models\User;
use Laravel\Mcp\Request;
use Laravel\Mcp\ResponseFactory;
+3 -3
View File
@@ -23,7 +23,7 @@ class CreateGroupToolTest extends TestCase
public function test_creates_group()
{
$name = 'Test MCP Group ' . uniqid();
$name = 'Test MCP Group '.uniqid();
$this->handle(['name' => $name]);
@@ -32,7 +32,7 @@ class CreateGroupToolTest extends TestCase
public function test_response_includes_id_and_name()
{
$name = 'Test MCP Group ' . uniqid();
$name = 'Test MCP Group '.uniqid();
$content = $this->handle(['name' => $name])->getStructuredContent();
@@ -51,7 +51,7 @@ class CreateGroupToolTest extends TestCase
{
$this->actingAs(User::factory()->create());
$name = 'Unauthorized Group ' . uniqid();
$name = 'Unauthorized Group '.uniqid();
$this->handle(['name' => $name]);
+2 -2
View File
@@ -25,7 +25,7 @@ class UpdateGroupToolTest extends TestCase
public function test_updates_group_by_id()
{
$group = Group::factory()->create();
$newNotes = 'Updated notes ' . uniqid();
$newNotes = 'Updated notes '.uniqid();
$this->handle(['id' => $group->id, 'notes' => $newNotes]);
@@ -35,7 +35,7 @@ class UpdateGroupToolTest extends TestCase
public function test_renames_via_new_name()
{
$group = Group::factory()->create();
$newName = 'Renamed Group ' . uniqid();
$newName = 'Renamed Group '.uniqid();
$this->handle(['id' => $group->id, 'new_name' => $newName]);