Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
eada5f5
Install laravel MCP
snipe May 7, 2026
84fea96
Added AssetBuilder to sequester scopes better
snipe May 7, 2026
7d57ce4
Added basic asset tools
snipe May 7, 2026
deb56f2
Added routes file
snipe May 7, 2026
91bd206
Added tests
snipe May 7, 2026
b731ec6
Added oauth routes to MCP
snipe May 7, 2026
490ce6f
Added passport oauth for mcp
snipe May 7, 2026
cc0169d
Use auth:api on routes
snipe May 7, 2026
0514901
Updated docs for laravel 12
snipe May 7, 2026
2f3df9a
Allow lookup by serial number
snipe May 7, 2026
656dae0
Added views
snipe May 7, 2026
6a47b4e
More tests
snipe May 7, 2026
51bdc3b
Added audit, delete and update tools
snipe May 7, 2026
d961714
Updated response
snipe May 7, 2026
0eec6e3
Fixed tests
snipe May 7, 2026
7636c24
TEMPORARILY remove api auth from MCP routes - this is breaking the in…
snipe May 7, 2026
b74e79b
Added user create, show, list, delete
snipe May 7, 2026
9aa5ba5
MCP for accessories management
snipe May 7, 2026
6b2f2d6
Add/delete/checkout/checkin/edit MCP tools for Components
snipe May 7, 2026
dc9f010
Gate checks and accessory scoping
snipe May 7, 2026
08b2d0c
Licenses MCP stuff
snipe May 8, 2026
664a190
Dept tooling
snipe May 8, 2026
2542221
Added tests
snipe May 8, 2026
9c97a06
Additional tools
snipe May 8, 2026
96a3a11
This doesn’t actually work yet
snipe May 8, 2026
c75d0ef
Pint :(
snipe May 8, 2026
8ccc705
Add a tool to update your own profile
snipe May 8, 2026
926f7dd
Added profile update tool
snipe May 8, 2026
ef4b234
Added common prompts
snipe May 8, 2026
4981817
Split name into two pieces
snipe May 8, 2026
4090e05
Pint
snipe May 8, 2026
aed11df
Added readme
snipe May 8, 2026
082ebeb
Localize prompts and tools
snipe May 8, 2026
e3a042f
More translations
snipe May 8, 2026
926afa6
Added throttle
snipe May 8, 2026
25a08fa
Updated readme
snipe May 8, 2026
b264e07
More localizations
snipe May 8, 2026
b8d2be6
Added test
snipe May 8, 2026
e839d98
Still more localizations
snipe May 8, 2026
d83b64f
Added tests
snipe May 8, 2026
19e58a8
Still more localization
snipe May 8, 2026
a56426e
And still more
snipe May 8, 2026
464db7f
Last one (I hope)
snipe May 8, 2026
256003b
Added password reset prompt
snipe May 9, 2026
f697ef1
Pint
snipe May 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# GitHub Copilot Custom Instructions for Snipe-IT

These instructions guide Copilot to generate code that aligns with modern Laravel 11 standards, PHP 8.2/8.4 features, software engineering principles, and industry best practices to improve software quality, maintainability, and security.
These instructions guide Copilot to generate code that aligns with modern Laravel 12 standards, PHP 8.2/8.4 features,
software engineering principles, and industry best practices to improve software quality, maintainability, and security.

## ✅ General Coding Standards

Expand All @@ -22,7 +23,7 @@
- Adopt **final classes** where extension is not intended.
- Use **Named Arguments** for improved clarity when calling functions with multiple parameters.

## ✅ Laravel 11 Project Structure & Conventions
## ✅ Laravel 12 Project Structure & Conventions

- Follow the official Laravel project structure:
- `app/Http/Controllers` - Controllers
Expand All @@ -32,6 +33,7 @@
- `app/Enums` - Enums
- `app/Actions` - Single-responsibility action classes
- `app/Policies` - Authorization logic
- `app/Models/Builders` - Query scoping logic

Check notice on line 36 in .github/copilot-instructions.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

.github/copilot-instructions.md#L36

Expected: 4; Actual: 2

- Controllers must:
- Use dependency injection.
Expand Down
45 changes: 45 additions & 0 deletions app/Mcp/Prompts/AuditLocationPrompt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Prompts\Argument;

#[Name('audit_location')]
#[Title('Audit Location')]
#[Description('Review all assets at a location, flag overdue audits and status anomalies')]
class AuditLocationPrompt extends SnipePrompt
{
public function handle(Request $request): Response
{
$location = $request->get('location');

$prompt = <<<TEXT
You are conducting an asset audit for location: {$location}.

Please complete the following steps using the available tools:

1. Find the location record for "{$location}" (search by name if needed).
2. List all assets currently assigned to or located at that location.
3. Identify any assets with overdue audit dates (next_audit_date is in the past).
4. Flag any assets with unexpected status labels (e.g. archived, pending, or out-for-repair assets that appear to still be at this location).
5. Note any assets that have been at this location longer than expected without a check-in or audit event.
6. Produce a summary report with: total asset count, assets requiring audit, assets with status anomalies, and any recommended actions.

Present the findings clearly so they can be acted on or exported.
TEXT;

return Response::text(trim($prompt).$this->localeInstruction());
}

public function arguments(): array
{
return [
new Argument('location', 'Name or ID of the location to audit', required: true),
];
}
}
54 changes: 54 additions & 0 deletions app/Mcp/Prompts/EndOfLifeReviewPrompt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Prompts\Argument;

#[Name('end_of_life_review')]
#[Title('End of Life Review')]
#[Description('Identify assets that have passed their EOL date or are fully depreciated, and recommend disposition actions')]
class EndOfLifeReviewPrompt extends SnipePrompt
{
public function handle(Request $request): Response
{
$department = $request->get('department');
$category = $request->get('category');

$scope = collect([
$department ? "department: {$department}" : null,
$category ? "category: {$category}" : null,
])->filter()->implode(' and ');

$scopeLine = $scope
? "Limit the review to assets in {$scope}."
: 'Review assets across the entire organisation.';

$prompt = <<<TEXT
You are conducting an end-of-life and depreciation review. {$scopeLine}

Please complete the following steps using the available tools:

1. List assets that have passed their asset_eol_date (end-of-life date is in the past).
2. List assets that are fully depreciated based on their depreciation schedule and purchase date.
3. For each identified asset, show: asset tag, name, model, assigned user or location, EOL date, purchase date, and current status.
4. Group findings by category for easier review.
5. Recommend disposition for each group: retire and replace, redeploy to a lower-demand role, send for repair, or archive.
6. Provide a cost summary if purchase cost data is available — total value of end-of-life assets.
TEXT;

return Response::text(trim($prompt).$this->localeInstruction());
}

public function arguments(): array
{
return [
new Argument('department', 'Limit review to a specific department', required: false),
new Argument('category', 'Limit review to a specific asset category', required: false),
];
}
}
43 changes: 43 additions & 0 deletions app/Mcp/Prompts/ExpiringLicensesPrompt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Prompts\Argument;

#[Name('expiring_licenses')]
#[Title('Expiring Licenses')]
#[Description('Review license seat usage and flag licenses expiring within a given number of days')]
class ExpiringLicensesPrompt extends SnipePrompt
{
public function handle(Request $request): Response
{
$days = (int) ($request->get('days', 30));

$prompt = <<<TEXT
You are reviewing software license health across the organisation. Focus on licenses expiring within {$days} days.

Please complete the following steps using the available tools:

1. List all licenses in the system.
2. Identify licenses whose expiration date falls within the next {$days} days.
3. For each expiring license, show: license name, total seats, seats in use, seats free, and the expiration date.
4. Flag any licenses that are over-deployed (more seats checked out than purchased).
5. Flag any licenses that are under-used (many free seats that may indicate unused subscriptions worth cancelling).
6. Produce a prioritised action list: renewals needed urgently, over-deployments to resolve, and possible cancellations.
TEXT;

return Response::text(trim($prompt).$this->localeInstruction());
}

public function arguments(): array
{
return [
new Argument('days', 'Number of days ahead to check for expiring licenses (default: 30)', required: false),
];
}
}
56 changes: 56 additions & 0 deletions app/Mcp/Prompts/FindAvailableAssetPrompt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Prompts\Argument;

#[Name('find_available_asset')]
#[Title('Find Available Asset')]
#[Description('Find an undeployed asset by category or model and optionally check it out to a user')]
class FindAvailableAssetPrompt extends SnipePrompt
{
public function handle(Request $request): Response
{
$category = $request->get('category');
$model = $request->get('model');
$assignTo = $request->get('assign_to');

$assetDescription = collect([
$category ? "category: {$category}" : null,
$model ? "model: {$model}" : null,
])->filter()->implode(' / ');

$assignLine = $assignTo
? "If a suitable asset is found, check it out to the user: {$assignTo}."
: 'Ask whether the found asset should be checked out to a specific user before proceeding.';

$prompt = <<<TEXT
You need to find an available (undeployed) asset matching {$assetDescription}.

Please complete the following steps using the available tools:

1. Search for assets with a Ready-to-Deploy status that match the requested {$assetDescription}.
2. If multiple options are available, list them with their asset tags, serial numbers, and any relevant details so the best one can be selected.
3. {$assignLine}
4. Confirm the final asset tag, serial number, and checkout status once complete.

If no available assets match, report what was found and suggest alternatives (different models in the same category, or assets currently out for repair that may return soon).
TEXT;

return Response::text(trim($prompt).$this->localeInstruction());
}

public function arguments(): array
{
return [
new Argument('category', 'Asset category to search (e.g. Laptop, Monitor)', required: false),
new Argument('model', 'Specific model name to search for', required: false),
new Argument('assign_to', 'Username to check the asset out to once found', required: false),
];
}
}
54 changes: 54 additions & 0 deletions app/Mcp/Prompts/InventorySummaryPrompt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Prompts\Argument;

#[Name('inventory_summary')]
#[Title('Inventory Summary')]
#[Description('Produce a high-level inventory count by category, broken down by deployment status')]
class InventorySummaryPrompt extends SnipePrompt
{
public function handle(Request $request): Response
{
$location = $request->get('location');
$department = $request->get('department');

$scope = collect([
$location ? "location: {$location}" : null,
$department ? "department: {$department}" : null,
])->filter()->implode(' and ');

$scopeLine = $scope
? "Scope the report to {$scope}."
: 'Report across the entire organisation.';

$prompt = <<<TEXT
You are generating an inventory summary report. {$scopeLine}

Please complete the following steps using the available tools:

1. List assets (filtered by the scope above if provided) and tally counts by status: Deployed, Ready to Deploy, Archived, Pending, Out for Repair.
2. Break the deployed count down by asset category (laptops, monitors, phones, etc.).
3. List the top 5 models by total quantity.
4. Show total purchase value of the inventory if cost data is available.
5. Highlight any categories with zero available (Ready to Deploy) assets — potential stock-out risk.
6. Present the results as a concise executive summary with a supporting breakdown table.
TEXT;

return Response::text(trim($prompt).$this->localeInstruction());
}

public function arguments(): array
{
return [
new Argument('location', 'Limit report to a specific location', required: false),
new Argument('department', 'Limit report to a specific department', required: false),
];
}
}
45 changes: 45 additions & 0 deletions app/Mcp/Prompts/OffboardEmployeePrompt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Prompts\Argument;

#[Name('offboard_employee')]
#[Title('Offboard Employee')]
#[Description('Guide through checking in all equipment and licenses from a departing employee and deactivating their account')]
class OffboardEmployeePrompt extends SnipePrompt
{
public function handle(Request $request): Response
{
$username = $request->get('username');

$prompt = <<<TEXT
You are helping offboard a departing employee with username: {$username}.

Please complete the following offboarding steps using the available tools:

1. Look up the user account for {$username} and display a summary of everything currently assigned to them (assets, licenses, accessories, consumables).
2. Check in all assigned assets from this user.
3. Check in all assigned accessories from this user.
4. Revoke or check in any license seats assigned to this user.
5. Deactivate the user account.
6. Provide a final summary of all items that were checked in and confirm the account has been deactivated.

If any items cannot be checked in automatically, flag them for manual follow-up.
TEXT;

return Response::text(trim($prompt).$this->localeInstruction());
}

public function arguments(): array
{
return [
new Argument('username', 'Username of the departing employee', required: true),
];
}
}
64 changes: 64 additions & 0 deletions app/Mcp/Prompts/OnboardEmployeePrompt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Prompts\Argument;

#[Name('onboard_employee')]
#[Title('Onboard Employee')]
#[Description('Guide through creating a new employee account and assigning appropriate equipment and licenses')]
class OnboardEmployeePrompt extends SnipePrompt
{
public function handle(Request $request): Response
{
$firstName = $request->get('first_name');
$lastName = $request->get('last_name');
$department = $request->get('department');
$location = $request->get('location');
$title = $request->get('title');

$fullName = trim("{$firstName} {$lastName}");

$context = collect([
$department ? "Department: {$department}" : null,
$location ? "Location: {$location}" : null,
$title ? "Job title: {$title}" : null,
])->filter()->implode("\n");

$prompt = <<<TEXT
You are helping onboard a new employee.

Employee details:
- First name: {$firstName}
- Last name: {$lastName}
{$context}

Please complete the following onboarding steps using the available tools:

1. Create a new user account using first_name "{$firstName}" and last_name "{$lastName}" along with the details provided above. Ask for any missing required fields (username and, optionally, email address) before proceeding. Do not ask for a password — one will be set automatically.
2. If the new account has an email address, ask whether you should send them a password reset link so they can set their own password. Use send_password_reset if the answer is yes.
3. Search for available (undeployed) assets suitable for their role — typically a laptop and any other standard equipment for their department or location.
4. Check out the selected assets to the new user.
5. Check whether any software license seats are available that should be assigned (e.g. productivity suites, VPN, etc.) and assign them.
6. Summarise what was set up: the user account created, whether a password reset email was sent, assets checked out, and licenses assigned.
TEXT;

return Response::text(trim($prompt).$this->localeInstruction());
}

public function arguments(): array
{
return [
new Argument('first_name', 'First name of the new employee', required: true),
new Argument('last_name', 'Last name of the new employee', required: false),
new Argument('department', 'Department the employee will join', required: false),
new Argument('location', 'Primary office location', required: false),
new Argument('title', 'Job title', required: false),
];
}
}
Loading
Loading