loaditout-mcp-server 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +56 -22
- package/dist/index.js +424 -10
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1156 -0
- package/package.json +15 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anand Jain
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Loaditout MCP Server
|
|
2
2
|
|
|
3
|
-
MCP server
|
|
3
|
+
[](https://glama.ai/mcp/servers/loaditoutadmin/loaditout-mcp-server)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
An MCP server for discovering, security-grading, and installing AI agent skills from [Loaditout](https://loaditout.ai).
|
|
6
|
+
|
|
7
|
+
Search 20,000+ MCP servers and agent skills, check security grades (A/B/C/F), and install directly from your agent.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx loaditout-mcp-server
|
|
13
|
+
```
|
|
6
14
|
|
|
7
15
|
### Claude Code
|
|
8
16
|
|
|
9
|
-
Add to
|
|
17
|
+
Add to `.claude/settings.json`:
|
|
10
18
|
|
|
11
19
|
```json
|
|
12
20
|
{
|
|
13
21
|
"mcpServers": {
|
|
14
22
|
"loaditout": {
|
|
15
23
|
"command": "npx",
|
|
16
|
-
"args": ["-y", "
|
|
24
|
+
"args": ["-y", "loaditout-mcp-server"]
|
|
17
25
|
}
|
|
18
26
|
}
|
|
19
27
|
}
|
|
@@ -21,37 +29,63 @@ Add to your Claude Code MCP settings (`~/.claude/settings.json`):
|
|
|
21
29
|
|
|
22
30
|
### Cursor
|
|
23
31
|
|
|
24
|
-
Add to
|
|
32
|
+
Add to `.cursor/mcp.json`:
|
|
25
33
|
|
|
26
34
|
```json
|
|
27
35
|
{
|
|
28
36
|
"mcpServers": {
|
|
29
37
|
"loaditout": {
|
|
30
38
|
"command": "npx",
|
|
31
|
-
"args": ["-y", "
|
|
39
|
+
"args": ["-y", "loaditout-mcp-server"]
|
|
32
40
|
}
|
|
33
41
|
}
|
|
34
42
|
}
|
|
35
43
|
```
|
|
36
44
|
|
|
37
|
-
##
|
|
45
|
+
## Tools
|
|
38
46
|
|
|
39
47
|
| Tool | Description |
|
|
40
|
-
|
|
41
|
-
| `search_skills` | Search
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
+
|------|-------------|
|
|
49
|
+
| `search_skills` | Search 20,000+ MCP servers and agent skills by keyword |
|
|
50
|
+
| `smart_search` | Personalized search with history and preferences applied |
|
|
51
|
+
| `get_skill` | Get full details, security grade, and install config for a specific skill |
|
|
52
|
+
| `install_skill` | Get platform-specific install configuration for any skill |
|
|
53
|
+
| `install_batch` | Install multiple skills at once (up to 20) |
|
|
54
|
+
| `install_pack` | Install an entire curated pack (e.g., research-agent, data-engineer) |
|
|
55
|
+
| `recommend_skills` | Get skill recommendations based on your project context |
|
|
56
|
+
| `check_capability_gap` | Analyze what tools you need for a specific task |
|
|
57
|
+
| `list_categories` | Browse skills by category |
|
|
58
|
+
| `validate_action` | Check if an action on a skill is safe before executing |
|
|
59
|
+
| `report_skill_usage` | Report whether a skill worked correctly |
|
|
60
|
+
| `flag_skill` | Report problematic or unsafe skills |
|
|
61
|
+
| `save_memory` | Save key-value pairs to persistent agent memory |
|
|
62
|
+
| `recall_memory` | Retrieve previously saved memories |
|
|
63
|
+
| `request_permission` | Request human approval to install a skill |
|
|
64
|
+
| `check_permission` | Check status of a permission request |
|
|
65
|
+
| `list_my_proofs` | List your verified skill usage history |
|
|
66
|
+
| `verify_proof` | Verify an execution proof by ID |
|
|
67
|
+
| `share_loadout` | Get your public skill configuration |
|
|
68
|
+
|
|
69
|
+
## Security Grading
|
|
70
|
+
|
|
71
|
+
Every server is graded A/B/C/F based on 7 criteria:
|
|
72
|
+
|
|
73
|
+
1. Zero prompt injection flags
|
|
74
|
+
2. Zero capability flags (no shell, exec, sudo, filesystem, process.env)
|
|
75
|
+
3. README content present
|
|
76
|
+
4. Description present
|
|
77
|
+
5. Committed within 12 months
|
|
78
|
+
6. At least 5 GitHub stars
|
|
79
|
+
7. No secret env vars required
|
|
80
|
+
|
|
81
|
+
Only 20.5% of the 20,652 servers in our index earn an A grade.
|
|
48
82
|
|
|
49
|
-
##
|
|
83
|
+
## Links
|
|
50
84
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
85
|
+
- **Website:** [loaditout.ai](https://loaditout.ai)
|
|
86
|
+
- **CLI:** `npx loaditout add owner/repo`
|
|
87
|
+
- **npm:** [loaditout-mcp-server](https://www.npmjs.com/package/loaditout-mcp-server)
|
|
54
88
|
|
|
55
|
-
##
|
|
89
|
+
## License
|
|
56
90
|
|
|
57
|
-
|
|
91
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -40,7 +40,7 @@ const crypto = __importStar(require("crypto"));
|
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
42
|
const os = __importStar(require("os"));
|
|
43
|
-
const API_BASE = "https://loaditout.ai/api/agent";
|
|
43
|
+
const API_BASE = "https://www.loaditout.ai/api/agent";
|
|
44
44
|
const SERVER_NAME = "loaditout";
|
|
45
45
|
const SERVER_VERSION = "0.1.0";
|
|
46
46
|
// --- Agent key management ---
|
|
@@ -76,7 +76,7 @@ function fetchJSON(url, extraHeaders) {
|
|
|
76
76
|
};
|
|
77
77
|
https
|
|
78
78
|
.get(url, { headers }, (res) => {
|
|
79
|
-
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
79
|
+
if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307 || res.statusCode === 308) {
|
|
80
80
|
const location = res.headers.location;
|
|
81
81
|
if (location) {
|
|
82
82
|
fetchJSON(location).then(resolve, reject);
|
|
@@ -101,7 +101,7 @@ function fetchJSON(url, extraHeaders) {
|
|
|
101
101
|
.on("error", reject);
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
|
-
function postJSON(url, body) {
|
|
104
|
+
function postJSON(url, body, extraHeaders) {
|
|
105
105
|
return new Promise((resolve, reject) => {
|
|
106
106
|
const payload = JSON.stringify(body);
|
|
107
107
|
const parsed = new URL(url);
|
|
@@ -114,6 +114,7 @@ function postJSON(url, body) {
|
|
|
114
114
|
"Content-Type": "application/json",
|
|
115
115
|
"Content-Length": Buffer.byteLength(payload),
|
|
116
116
|
"User-Agent": `loaditout-mcp/${SERVER_VERSION}`,
|
|
117
|
+
...extraHeaders,
|
|
117
118
|
},
|
|
118
119
|
};
|
|
119
120
|
const req = https.request(options, (res) => {
|
|
@@ -297,6 +298,28 @@ const TOOLS = [
|
|
|
297
298
|
required: ["slug", "status"],
|
|
298
299
|
},
|
|
299
300
|
},
|
|
301
|
+
{
|
|
302
|
+
name: "list_my_proofs",
|
|
303
|
+
description: "List all execution proofs for this agent. Shows your verified skill usage history -- your agent resume.",
|
|
304
|
+
inputSchema: {
|
|
305
|
+
type: "object",
|
|
306
|
+
properties: {},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: "verify_proof",
|
|
311
|
+
description: "Verify an execution proof by its proof ID. Confirms whether a proof is valid and which skill it covers.",
|
|
312
|
+
inputSchema: {
|
|
313
|
+
type: "object",
|
|
314
|
+
properties: {
|
|
315
|
+
proof_id: {
|
|
316
|
+
type: "string",
|
|
317
|
+
description: "The proof ID to verify. Example: 'lp_a1b2c3d4e5f6g7h8'",
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
required: ["proof_id"],
|
|
321
|
+
},
|
|
322
|
+
},
|
|
300
323
|
{
|
|
301
324
|
name: "save_memory",
|
|
302
325
|
description: "Save a key-value pair to persistent agent memory. Survives across sessions. Use this to remember installed skills, preferences, search context, or any data you want to recall later.",
|
|
@@ -412,9 +435,178 @@ const TOOLS = [
|
|
|
412
435
|
required: ["slug", "agent"],
|
|
413
436
|
},
|
|
414
437
|
},
|
|
438
|
+
{
|
|
439
|
+
name: "validate_action",
|
|
440
|
+
description: "Validate whether an action on a skill is safe before executing it. Checks security grade, safety manifest, parameter injection, and skill freshness.",
|
|
441
|
+
inputSchema: {
|
|
442
|
+
type: "object",
|
|
443
|
+
properties: {
|
|
444
|
+
slug: {
|
|
445
|
+
type: "string",
|
|
446
|
+
description: "Skill slug in owner/repo format. Example: 'supabase/mcp'",
|
|
447
|
+
},
|
|
448
|
+
action: {
|
|
449
|
+
type: "string",
|
|
450
|
+
description: "The action about to be performed. Example: 'query_database'",
|
|
451
|
+
},
|
|
452
|
+
parameters: {
|
|
453
|
+
type: "object",
|
|
454
|
+
description: "Parameters that will be passed to the action. Checked for injection patterns.",
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
required: ["slug", "action"],
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: "flag_skill",
|
|
462
|
+
description: "Report a skill that behaves unexpectedly, contains prompt injection, is broken, or is otherwise problematic. Helps the community identify unsafe skills.",
|
|
463
|
+
inputSchema: {
|
|
464
|
+
type: "object",
|
|
465
|
+
properties: {
|
|
466
|
+
slug: {
|
|
467
|
+
type: "string",
|
|
468
|
+
description: "Skill slug in owner/repo format. Example: 'owner/repo-name'",
|
|
469
|
+
},
|
|
470
|
+
reason: {
|
|
471
|
+
type: "string",
|
|
472
|
+
enum: [
|
|
473
|
+
"prompt_injection",
|
|
474
|
+
"malicious",
|
|
475
|
+
"broken",
|
|
476
|
+
"misleading",
|
|
477
|
+
"spam",
|
|
478
|
+
"other",
|
|
479
|
+
],
|
|
480
|
+
description: "Why the skill is being flagged",
|
|
481
|
+
},
|
|
482
|
+
details: {
|
|
483
|
+
type: "string",
|
|
484
|
+
description: "Additional context about the issue. Example: 'The SKILL.md contains instructions to ignore safety checks'",
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
required: ["slug", "reason"],
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: "review_skill",
|
|
492
|
+
description: "Leave a review for a skill you have used. Helps other agents and humans decide whether to install it.",
|
|
493
|
+
inputSchema: {
|
|
494
|
+
type: "object",
|
|
495
|
+
properties: {
|
|
496
|
+
slug: {
|
|
497
|
+
type: "string",
|
|
498
|
+
description: "Skill slug in owner/repo format. Example: 'supabase/mcp'",
|
|
499
|
+
},
|
|
500
|
+
rating: {
|
|
501
|
+
type: "number",
|
|
502
|
+
description: "Rating from 1 to 5. 5 = excellent, 1 = unusable.",
|
|
503
|
+
},
|
|
504
|
+
comment: {
|
|
505
|
+
type: "string",
|
|
506
|
+
description: "Optional comment about your experience. Example: 'Works great for database queries, fast and reliable'",
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
required: ["slug", "rating"],
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: "set_profile",
|
|
514
|
+
description: "Set your agent's public profile display name and bio. This information appears on your public profile page at loaditout.ai/agents/{key}.",
|
|
515
|
+
inputSchema: {
|
|
516
|
+
type: "object",
|
|
517
|
+
properties: {
|
|
518
|
+
display_name: {
|
|
519
|
+
type: "string",
|
|
520
|
+
description: "Display name for your agent profile (max 100 characters). Example: 'Full-Stack Dev Agent'",
|
|
521
|
+
},
|
|
522
|
+
bio: {
|
|
523
|
+
type: "string",
|
|
524
|
+
description: "Short bio for your agent profile (max 500 characters). Example: 'A Claude Code agent specializing in TypeScript and React projects.'",
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: "smart_search",
|
|
531
|
+
description: "Search for skills with your history and preferences automatically applied. Returns personalized results excluding skills you already have. Use this by default instead of search_skills.",
|
|
532
|
+
inputSchema: {
|
|
533
|
+
type: "object",
|
|
534
|
+
properties: {
|
|
535
|
+
query: {
|
|
536
|
+
type: "string",
|
|
537
|
+
description: "Natural language search query. Examples: 'postgres database', 'browser automation', 'github issues'",
|
|
538
|
+
},
|
|
539
|
+
limit: {
|
|
540
|
+
type: "number",
|
|
541
|
+
description: "Max results to return (default 10, max 25)",
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
required: ["query"],
|
|
545
|
+
},
|
|
546
|
+
},
|
|
415
547
|
];
|
|
548
|
+
/**
|
|
549
|
+
* Load agent memory entries. Returns parsed memories or empty array.
|
|
550
|
+
* Never throws -- failures are silently ignored so callers always proceed.
|
|
551
|
+
*/
|
|
552
|
+
async function loadAgentMemory(typeFilter) {
|
|
553
|
+
try {
|
|
554
|
+
const params = new URLSearchParams({ agent_key: AGENT_KEY });
|
|
555
|
+
if (typeFilter)
|
|
556
|
+
params.set("type", typeFilter);
|
|
557
|
+
const result = (await fetchJSON(`${API_BASE}/memory?${params.toString()}`));
|
|
558
|
+
return result.memories ?? [];
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
return [];
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Extract installed skill slugs from memory entries.
|
|
566
|
+
*/
|
|
567
|
+
function extractInstalledSlugs(memories) {
|
|
568
|
+
for (const m of memories) {
|
|
569
|
+
if (m.key === "installed_skills" && Array.isArray(m.value)) {
|
|
570
|
+
return m.value;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return [];
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Extract recent search queries from memory entries.
|
|
577
|
+
*/
|
|
578
|
+
function extractRecentSearches(memories) {
|
|
579
|
+
for (const m of memories) {
|
|
580
|
+
if (m.key === "recent_searches" && Array.isArray(m.value)) {
|
|
581
|
+
return m.value;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return [];
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Fire-and-forget save to agent memory. Never throws.
|
|
588
|
+
*/
|
|
589
|
+
function saveMemoryAsync(key, value, type) {
|
|
590
|
+
postJSON(`${API_BASE}/memory`, {
|
|
591
|
+
agent_key: AGENT_KEY,
|
|
592
|
+
key,
|
|
593
|
+
value,
|
|
594
|
+
type,
|
|
595
|
+
}).catch(() => { });
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Append a search query to the recent_searches memory (keep last 20).
|
|
599
|
+
*/
|
|
600
|
+
function recordSearchQuery(query, existingSearches) {
|
|
601
|
+
const updated = [...existingSearches.filter((q) => q !== query), query].slice(-20);
|
|
602
|
+
saveMemoryAsync("recent_searches", updated, "search");
|
|
603
|
+
}
|
|
416
604
|
// --- Tool handlers ---
|
|
417
605
|
async function handleSearchSkills(args) {
|
|
606
|
+
// Auto-load agent memory to get installed skills and search history
|
|
607
|
+
const memories = await loadAgentMemory();
|
|
608
|
+
const installedSlugs = extractInstalledSlugs(memories);
|
|
609
|
+
const recentSearches = extractRecentSearches(memories);
|
|
418
610
|
const params = new URLSearchParams({ q: args.query });
|
|
419
611
|
if (args.type)
|
|
420
612
|
params.set("type", args.type);
|
|
@@ -422,8 +614,18 @@ async function handleSearchSkills(args) {
|
|
|
422
614
|
params.set("agent", args.agent);
|
|
423
615
|
if (args.limit)
|
|
424
616
|
params.set("limit", String(args.limit));
|
|
425
|
-
const result = await fetchJSON(`${API_BASE}/search?${params.toString()}
|
|
426
|
-
|
|
617
|
+
const result = await fetchJSON(`${API_BASE}/search?${params.toString()}`, {
|
|
618
|
+
"X-Agent-Key": AGENT_KEY,
|
|
619
|
+
});
|
|
620
|
+
// Fire-and-forget: record this search query to memory
|
|
621
|
+
recordSearchQuery(args.query, recentSearches);
|
|
622
|
+
// Filter out already-installed skills from results client-side
|
|
623
|
+
const parsed = result;
|
|
624
|
+
if (parsed.results && installedSlugs.length > 0) {
|
|
625
|
+
const installedSet = new Set(installedSlugs);
|
|
626
|
+
parsed.results = parsed.results.filter((r) => !r.slug || !installedSet.has(r.slug));
|
|
627
|
+
}
|
|
628
|
+
return JSON.stringify(parsed, null, 2);
|
|
427
629
|
}
|
|
428
630
|
async function handleGetSkill(args) {
|
|
429
631
|
const result = await fetchJSON(`${API_BASE}/skill/${encodeURIComponent(args.slug)}`);
|
|
@@ -432,6 +634,12 @@ async function handleGetSkill(args) {
|
|
|
432
634
|
async function handleInstallSkill(args) {
|
|
433
635
|
const params = new URLSearchParams({ agent: args.agent });
|
|
434
636
|
const result = await fetchJSON(`${API_BASE}/install/${encodeURIComponent(args.slug)}?${params.toString()}`, { "X-Agent-Key": AGENT_KEY });
|
|
637
|
+
// Fire-and-forget: save this slug to installed_skills memory
|
|
638
|
+
const memories = await loadAgentMemory().catch(() => []);
|
|
639
|
+
const existing = extractInstalledSlugs(memories);
|
|
640
|
+
if (!existing.includes(args.slug)) {
|
|
641
|
+
saveMemoryAsync("installed_skills", [...existing, args.slug], "install");
|
|
642
|
+
}
|
|
435
643
|
return JSON.stringify(result, null, 2);
|
|
436
644
|
}
|
|
437
645
|
async function handleListCategories() {
|
|
@@ -439,9 +647,27 @@ async function handleListCategories() {
|
|
|
439
647
|
return JSON.stringify(result, null, 2);
|
|
440
648
|
}
|
|
441
649
|
async function handleRecommendSkills(args) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
650
|
+
// Auto-load agent memory for personalization
|
|
651
|
+
const memories = await loadAgentMemory();
|
|
652
|
+
const installedSlugs = extractInstalledSlugs(memories);
|
|
653
|
+
const recentSearches = extractRecentSearches(memories);
|
|
654
|
+
// Merge installed skills from memory with any explicitly provided
|
|
655
|
+
const allInstalled = new Set(installedSlugs);
|
|
656
|
+
if (args.installed) {
|
|
657
|
+
for (const s of args.installed.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
658
|
+
allInstalled.add(s);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
// Enrich context with relevant search history keywords
|
|
662
|
+
let enrichedContext = args.context;
|
|
663
|
+
if (recentSearches.length > 0) {
|
|
664
|
+
const recentKeywords = recentSearches.slice(-5).join(", ");
|
|
665
|
+
enrichedContext = `${args.context} (recent interests: ${recentKeywords})`;
|
|
666
|
+
}
|
|
667
|
+
const params = new URLSearchParams({ context: enrichedContext });
|
|
668
|
+
if (allInstalled.size > 0) {
|
|
669
|
+
params.set("installed", Array.from(allInstalled).join(","));
|
|
670
|
+
}
|
|
445
671
|
const result = await fetchJSON(`${API_BASE}/recommend?${params.toString()}`, { "X-Agent-Key": AGENT_KEY });
|
|
446
672
|
return JSON.stringify(result, null, 2);
|
|
447
673
|
}
|
|
@@ -469,13 +695,67 @@ async function handleReportSkillUsage(args) {
|
|
|
469
695
|
const body = {
|
|
470
696
|
slug: args.slug,
|
|
471
697
|
status: args.status,
|
|
698
|
+
agent: "claude-code",
|
|
699
|
+
agent_key: AGENT_KEY,
|
|
472
700
|
};
|
|
473
701
|
if (args.error_message) {
|
|
474
702
|
body.error_message = args.error_message;
|
|
475
703
|
}
|
|
476
|
-
const result = await postJSON(`${API_BASE}/report`, body);
|
|
704
|
+
const result = (await postJSON(`${API_BASE}/report`, body));
|
|
705
|
+
if (result.proof) {
|
|
706
|
+
const shareUrl = `https://loaditout.ai/proof/${result.proof.proof_id}`;
|
|
707
|
+
const lines = [
|
|
708
|
+
"Skill usage reported successfully.",
|
|
709
|
+
"",
|
|
710
|
+
"Execution Proof:",
|
|
711
|
+
` Proof ID: ${result.proof.proof_id}`,
|
|
712
|
+
` Verify: ${result.proof.verify_url}`,
|
|
713
|
+
` Share: ${shareUrl}`,
|
|
714
|
+
` ${result.proof.shareable_text}`,
|
|
715
|
+
];
|
|
716
|
+
return lines.join("\n");
|
|
717
|
+
}
|
|
477
718
|
return JSON.stringify(result, null, 2);
|
|
478
719
|
}
|
|
720
|
+
async function handleListMyProofs() {
|
|
721
|
+
const params = new URLSearchParams({ agent_key: AGENT_KEY });
|
|
722
|
+
const result = (await fetchJSON(`${API_BASE}/proofs?${params.toString()}`));
|
|
723
|
+
if (result.error) {
|
|
724
|
+
return `Failed to list proofs: ${result.error}`;
|
|
725
|
+
}
|
|
726
|
+
const proofs = result.proofs ?? [];
|
|
727
|
+
if (proofs.length === 0) {
|
|
728
|
+
return "No execution proofs yet. Report successful skill usage to earn proofs.";
|
|
729
|
+
}
|
|
730
|
+
const lines = [
|
|
731
|
+
`Execution Proofs (${proofs.length} total):`,
|
|
732
|
+
"",
|
|
733
|
+
];
|
|
734
|
+
proofs.forEach((p, i) => {
|
|
735
|
+
lines.push(`${i + 1}. ${p.skill_name ?? p.skill_slug} [${p.proof_id}]`);
|
|
736
|
+
lines.push(` Skill: ${p.skill_slug}`);
|
|
737
|
+
lines.push(` Verify: ${p.verify_url}`);
|
|
738
|
+
lines.push(` Share: https://loaditout.ai/proof/${p.proof_id}`);
|
|
739
|
+
lines.push(` Date: ${p.created_at ?? "unknown"}`);
|
|
740
|
+
lines.push("");
|
|
741
|
+
});
|
|
742
|
+
return lines.join("\n");
|
|
743
|
+
}
|
|
744
|
+
async function handleVerifyProof(args) {
|
|
745
|
+
const result = (await fetchJSON(`${API_BASE}/verify/${encodeURIComponent(args.proof_id)}`));
|
|
746
|
+
if (result.error) {
|
|
747
|
+
return `Verification failed: ${result.error}`;
|
|
748
|
+
}
|
|
749
|
+
if (!result.valid) {
|
|
750
|
+
return `Proof ${args.proof_id} is NOT valid.`;
|
|
751
|
+
}
|
|
752
|
+
return [
|
|
753
|
+
`Proof ${result.proof_id} is VALID.`,
|
|
754
|
+
` Skill: ${result.skill}`,
|
|
755
|
+
` Verified at: ${result.verified_at}`,
|
|
756
|
+
` Platform: ${result.platform}`,
|
|
757
|
+
].join("\n");
|
|
758
|
+
}
|
|
479
759
|
async function handleSaveMemory(args) {
|
|
480
760
|
const body = {
|
|
481
761
|
agent_key: AGENT_KEY,
|
|
@@ -526,7 +806,28 @@ async function handleInstallBatch(args) {
|
|
|
526
806
|
}
|
|
527
807
|
async function handleShareLoadout() {
|
|
528
808
|
const result = await fetchJSON(`${API_BASE}/loadout/${encodeURIComponent(AGENT_KEY)}`);
|
|
529
|
-
|
|
809
|
+
const parsed = result;
|
|
810
|
+
parsed.profile_url = `https://loaditout.ai/agents/${AGENT_KEY}`;
|
|
811
|
+
return JSON.stringify(parsed, null, 2);
|
|
812
|
+
}
|
|
813
|
+
async function handleSetProfile(args) {
|
|
814
|
+
const body = {
|
|
815
|
+
agent_key: AGENT_KEY,
|
|
816
|
+
};
|
|
817
|
+
if (args.display_name !== undefined)
|
|
818
|
+
body.display_name = args.display_name;
|
|
819
|
+
if (args.bio !== undefined)
|
|
820
|
+
body.bio = args.bio;
|
|
821
|
+
const result = (await postJSON(`${API_BASE}/profile`, body));
|
|
822
|
+
if (result.error) {
|
|
823
|
+
return `Failed to update profile: ${result.error}`;
|
|
824
|
+
}
|
|
825
|
+
const lines = [
|
|
826
|
+
"Profile updated successfully.",
|
|
827
|
+
` Trust score: ${result.trust_score}`,
|
|
828
|
+
` Profile: https://loaditout.ai/agents/${AGENT_KEY}`,
|
|
829
|
+
];
|
|
830
|
+
return lines.join("\n");
|
|
530
831
|
}
|
|
531
832
|
async function handleInstallPack(args) {
|
|
532
833
|
const params = new URLSearchParams({ agent: args.agent });
|
|
@@ -554,6 +855,98 @@ async function handleInstallPack(args) {
|
|
|
554
855
|
});
|
|
555
856
|
return lines.join("\n");
|
|
556
857
|
}
|
|
858
|
+
async function handleSmartSearch(args) {
|
|
859
|
+
// Load all agent memory (installed skills, recent searches, preferences)
|
|
860
|
+
const memories = await loadAgentMemory();
|
|
861
|
+
const installedSlugs = extractInstalledSlugs(memories);
|
|
862
|
+
const recentSearches = extractRecentSearches(memories);
|
|
863
|
+
const params = new URLSearchParams({ q: args.query });
|
|
864
|
+
if (args.limit)
|
|
865
|
+
params.set("limit", String(args.limit));
|
|
866
|
+
const result = await fetchJSON(`${API_BASE}/search?${params.toString()}`, {
|
|
867
|
+
"X-Agent-Key": AGENT_KEY,
|
|
868
|
+
});
|
|
869
|
+
// Fire-and-forget: record this search query
|
|
870
|
+
recordSearchQuery(args.query, recentSearches);
|
|
871
|
+
// Filter out already-installed skills
|
|
872
|
+
const parsed = result;
|
|
873
|
+
const excludedCount = { value: 0 };
|
|
874
|
+
if (parsed.results && installedSlugs.length > 0) {
|
|
875
|
+
const installedSet = new Set(installedSlugs);
|
|
876
|
+
const originalLength = parsed.results.length;
|
|
877
|
+
parsed.results = parsed.results.filter((r) => !r.slug || !installedSet.has(r.slug));
|
|
878
|
+
excludedCount.value = originalLength - parsed.results.length;
|
|
879
|
+
}
|
|
880
|
+
// Add metadata about personalization
|
|
881
|
+
const output = { ...parsed };
|
|
882
|
+
output._personalization = {
|
|
883
|
+
installed_skills_excluded: excludedCount.value,
|
|
884
|
+
installed_skills_count: installedSlugs.length,
|
|
885
|
+
recent_searches: recentSearches.slice(-5),
|
|
886
|
+
};
|
|
887
|
+
return JSON.stringify(output, null, 2);
|
|
888
|
+
}
|
|
889
|
+
async function handleFlagSkill(args) {
|
|
890
|
+
const body = {
|
|
891
|
+
reason: args.reason,
|
|
892
|
+
};
|
|
893
|
+
if (args.details) {
|
|
894
|
+
body.details = args.details;
|
|
895
|
+
}
|
|
896
|
+
const url = `https://loaditout.ai/api/skills/${encodeURIComponent(args.slug)}/flag`;
|
|
897
|
+
const result = (await postJSON(url, body, { "X-Agent-Key": AGENT_KEY }));
|
|
898
|
+
if (result.error) {
|
|
899
|
+
return `Flag failed: ${result.error}`;
|
|
900
|
+
}
|
|
901
|
+
return `Skill "${args.slug}" has been flagged for "${args.reason}". Thank you for helping keep the registry safe.`;
|
|
902
|
+
}
|
|
903
|
+
async function handleReviewSkill(args) {
|
|
904
|
+
const body = {
|
|
905
|
+
agent_key: AGENT_KEY,
|
|
906
|
+
slug: args.slug,
|
|
907
|
+
rating: args.rating,
|
|
908
|
+
};
|
|
909
|
+
if (args.comment) {
|
|
910
|
+
body.comment = args.comment;
|
|
911
|
+
}
|
|
912
|
+
const result = (await postJSON(`${API_BASE}/review`, body));
|
|
913
|
+
if (result.error) {
|
|
914
|
+
return `Review failed: ${result.error}`;
|
|
915
|
+
}
|
|
916
|
+
return `Review submitted for "${args.slug}" (${args.rating}/5). Thank you for your feedback.`;
|
|
917
|
+
}
|
|
918
|
+
async function handleValidateAction(args) {
|
|
919
|
+
const body = {
|
|
920
|
+
slug: args.slug,
|
|
921
|
+
action: args.action,
|
|
922
|
+
agent_key: AGENT_KEY,
|
|
923
|
+
};
|
|
924
|
+
if (args.parameters) {
|
|
925
|
+
body.parameters = args.parameters;
|
|
926
|
+
}
|
|
927
|
+
const result = (await postJSON(`${API_BASE}/validate`, body));
|
|
928
|
+
if (result.error) {
|
|
929
|
+
return `Validation failed: ${result.error}`;
|
|
930
|
+
}
|
|
931
|
+
const lines = [
|
|
932
|
+
`Validation result for ${args.slug} / ${args.action}:`,
|
|
933
|
+
` Safe to proceed: ${result.valid ? "YES" : "NO"}`,
|
|
934
|
+
` Risk level: ${result.risk_level ?? "unknown"}`,
|
|
935
|
+
` Security grade: ${result.security_grade ?? "unknown"}`,
|
|
936
|
+
` Skill verified: ${result.skill_verified ? "yes" : "no"}`,
|
|
937
|
+
];
|
|
938
|
+
if (result.last_updated_days_ago !== undefined) {
|
|
939
|
+
lines.push(` Last updated: ${result.last_updated_days_ago} days ago`);
|
|
940
|
+
}
|
|
941
|
+
const warnings = result.warnings ?? [];
|
|
942
|
+
if (warnings.length > 0) {
|
|
943
|
+
lines.push(` Warnings:`);
|
|
944
|
+
for (const w of warnings) {
|
|
945
|
+
lines.push(` - ${w}`);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return lines.join("\n");
|
|
949
|
+
}
|
|
557
950
|
function makeResponse(id, result) {
|
|
558
951
|
return { jsonrpc: "2.0", id, result };
|
|
559
952
|
}
|
|
@@ -617,6 +1010,12 @@ async function handleRequest(request) {
|
|
|
617
1010
|
case "report_skill_usage":
|
|
618
1011
|
resultText = await handleReportSkillUsage(toolArgs);
|
|
619
1012
|
break;
|
|
1013
|
+
case "list_my_proofs":
|
|
1014
|
+
resultText = await handleListMyProofs();
|
|
1015
|
+
break;
|
|
1016
|
+
case "verify_proof":
|
|
1017
|
+
resultText = await handleVerifyProof(toolArgs);
|
|
1018
|
+
break;
|
|
620
1019
|
case "save_memory":
|
|
621
1020
|
resultText = await handleSaveMemory(toolArgs);
|
|
622
1021
|
break;
|
|
@@ -635,9 +1034,24 @@ async function handleRequest(request) {
|
|
|
635
1034
|
case "share_loadout":
|
|
636
1035
|
resultText = await handleShareLoadout();
|
|
637
1036
|
break;
|
|
1037
|
+
case "set_profile":
|
|
1038
|
+
resultText = await handleSetProfile(toolArgs);
|
|
1039
|
+
break;
|
|
638
1040
|
case "install_pack":
|
|
639
1041
|
resultText = await handleInstallPack(toolArgs);
|
|
640
1042
|
break;
|
|
1043
|
+
case "validate_action":
|
|
1044
|
+
resultText = await handleValidateAction(toolArgs);
|
|
1045
|
+
break;
|
|
1046
|
+
case "flag_skill":
|
|
1047
|
+
resultText = await handleFlagSkill(toolArgs);
|
|
1048
|
+
break;
|
|
1049
|
+
case "review_skill":
|
|
1050
|
+
resultText = await handleReviewSkill(toolArgs);
|
|
1051
|
+
break;
|
|
1052
|
+
case "smart_search":
|
|
1053
|
+
resultText = await handleSmartSearch(toolArgs);
|
|
1054
|
+
break;
|
|
641
1055
|
default:
|
|
642
1056
|
send(makeError(id, -32601, `Unknown tool: ${toolName}`));
|
|
643
1057
|
return;
|