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 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
- # @loaditout/mcp-server
1
+ # Loaditout MCP Server
2
2
 
3
- MCP server for discovering, installing, and managing AI agent skills from the Loaditout registry. It connects your agent to a curated catalog of MCP servers and SKILL.md behaviors, with search, recommendations, and usage reporting.
3
+ [![loaditout-mcp-server MCP server](https://glama.ai/mcp/servers/loaditoutadmin/loaditout-mcp-server/badges/card.svg)](https://glama.ai/mcp/servers/loaditoutadmin/loaditout-mcp-server)
4
4
 
5
- ## Installation
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 your Claude Code MCP settings (`~/.claude/settings.json`):
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", "@loaditout/mcp-server"]
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 your Cursor MCP config (`.cursor/mcp.json` in your project or `~/.cursor/mcp.json` globally):
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", "@loaditout/mcp-server"]
39
+ "args": ["-y", "loaditout-mcp-server"]
32
40
  }
33
41
  }
34
42
  }
35
43
  ```
36
44
 
37
- ## Available Tools
45
+ ## Tools
38
46
 
39
47
  | Tool | Description |
40
- |---|---|
41
- | `search_skills` | Search the registry for skills by natural language query. Supports filtering by type and agent platform. |
42
- | `get_skill` | Get full details for a specific skill by its slug (owner/repo format). |
43
- | `install_skill` | Get the exact configuration JSON needed to install a skill for a specific agent. Returns config and instructions but does not write files. |
44
- | `list_categories` | List all skill categories with descriptions and skill counts. |
45
- | `recommend_skills` | Get skill recommendations based on a description of your project or task. |
46
- | `check_capability_gap` | Analyze what the agent is trying to do and suggest skills that would help. Designed for when you encounter a task you cannot complete or need specialized tools for. |
47
- | `report_skill_usage` | Report whether an installed skill worked correctly. Helps improve quality scores across the registry. |
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
- ## Resources
83
+ ## Links
50
84
 
51
- | URI | Description |
52
- |---|---|
53
- | `loaditout://catalog` | Browse the full skill catalog organized by category with counts. |
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
- ## Zero Dependencies
89
+ ## License
56
90
 
57
- This server uses only Node.js built-in modules (https, readline) for HTTP communication. No external packages are required.
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
- return JSON.stringify(result, null, 2);
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
- const params = new URLSearchParams({ context: args.context });
443
- if (args.installed)
444
- params.set("installed", args.installed);
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
- return JSON.stringify(result, null, 2);
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;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};