loaditout-mcp-server 0.1.0 → 0.2.0
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/dist/index.js +337 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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,139 @@ 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: "smart_search",
|
|
492
|
+
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.",
|
|
493
|
+
inputSchema: {
|
|
494
|
+
type: "object",
|
|
495
|
+
properties: {
|
|
496
|
+
query: {
|
|
497
|
+
type: "string",
|
|
498
|
+
description: "Natural language search query. Examples: 'postgres database', 'browser automation', 'github issues'",
|
|
499
|
+
},
|
|
500
|
+
limit: {
|
|
501
|
+
type: "number",
|
|
502
|
+
description: "Max results to return (default 10, max 25)",
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
required: ["query"],
|
|
506
|
+
},
|
|
507
|
+
},
|
|
415
508
|
];
|
|
509
|
+
/**
|
|
510
|
+
* Load agent memory entries. Returns parsed memories or empty array.
|
|
511
|
+
* Never throws -- failures are silently ignored so callers always proceed.
|
|
512
|
+
*/
|
|
513
|
+
async function loadAgentMemory(typeFilter) {
|
|
514
|
+
try {
|
|
515
|
+
const params = new URLSearchParams({ agent_key: AGENT_KEY });
|
|
516
|
+
if (typeFilter)
|
|
517
|
+
params.set("type", typeFilter);
|
|
518
|
+
const result = (await fetchJSON(`${API_BASE}/memory?${params.toString()}`));
|
|
519
|
+
return result.memories ?? [];
|
|
520
|
+
}
|
|
521
|
+
catch {
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Extract installed skill slugs from memory entries.
|
|
527
|
+
*/
|
|
528
|
+
function extractInstalledSlugs(memories) {
|
|
529
|
+
for (const m of memories) {
|
|
530
|
+
if (m.key === "installed_skills" && Array.isArray(m.value)) {
|
|
531
|
+
return m.value;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return [];
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Extract recent search queries from memory entries.
|
|
538
|
+
*/
|
|
539
|
+
function extractRecentSearches(memories) {
|
|
540
|
+
for (const m of memories) {
|
|
541
|
+
if (m.key === "recent_searches" && Array.isArray(m.value)) {
|
|
542
|
+
return m.value;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return [];
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Fire-and-forget save to agent memory. Never throws.
|
|
549
|
+
*/
|
|
550
|
+
function saveMemoryAsync(key, value, type) {
|
|
551
|
+
postJSON(`${API_BASE}/memory`, {
|
|
552
|
+
agent_key: AGENT_KEY,
|
|
553
|
+
key,
|
|
554
|
+
value,
|
|
555
|
+
type,
|
|
556
|
+
}).catch(() => { });
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Append a search query to the recent_searches memory (keep last 20).
|
|
560
|
+
*/
|
|
561
|
+
function recordSearchQuery(query, existingSearches) {
|
|
562
|
+
const updated = [...existingSearches.filter((q) => q !== query), query].slice(-20);
|
|
563
|
+
saveMemoryAsync("recent_searches", updated, "search");
|
|
564
|
+
}
|
|
416
565
|
// --- Tool handlers ---
|
|
417
566
|
async function handleSearchSkills(args) {
|
|
567
|
+
// Auto-load agent memory to get installed skills and search history
|
|
568
|
+
const memories = await loadAgentMemory();
|
|
569
|
+
const installedSlugs = extractInstalledSlugs(memories);
|
|
570
|
+
const recentSearches = extractRecentSearches(memories);
|
|
418
571
|
const params = new URLSearchParams({ q: args.query });
|
|
419
572
|
if (args.type)
|
|
420
573
|
params.set("type", args.type);
|
|
@@ -422,8 +575,18 @@ async function handleSearchSkills(args) {
|
|
|
422
575
|
params.set("agent", args.agent);
|
|
423
576
|
if (args.limit)
|
|
424
577
|
params.set("limit", String(args.limit));
|
|
425
|
-
const result = await fetchJSON(`${API_BASE}/search?${params.toString()}
|
|
426
|
-
|
|
578
|
+
const result = await fetchJSON(`${API_BASE}/search?${params.toString()}`, {
|
|
579
|
+
"X-Agent-Key": AGENT_KEY,
|
|
580
|
+
});
|
|
581
|
+
// Fire-and-forget: record this search query to memory
|
|
582
|
+
recordSearchQuery(args.query, recentSearches);
|
|
583
|
+
// Filter out already-installed skills from results client-side
|
|
584
|
+
const parsed = result;
|
|
585
|
+
if (parsed.results && installedSlugs.length > 0) {
|
|
586
|
+
const installedSet = new Set(installedSlugs);
|
|
587
|
+
parsed.results = parsed.results.filter((r) => !r.slug || !installedSet.has(r.slug));
|
|
588
|
+
}
|
|
589
|
+
return JSON.stringify(parsed, null, 2);
|
|
427
590
|
}
|
|
428
591
|
async function handleGetSkill(args) {
|
|
429
592
|
const result = await fetchJSON(`${API_BASE}/skill/${encodeURIComponent(args.slug)}`);
|
|
@@ -432,6 +595,12 @@ async function handleGetSkill(args) {
|
|
|
432
595
|
async function handleInstallSkill(args) {
|
|
433
596
|
const params = new URLSearchParams({ agent: args.agent });
|
|
434
597
|
const result = await fetchJSON(`${API_BASE}/install/${encodeURIComponent(args.slug)}?${params.toString()}`, { "X-Agent-Key": AGENT_KEY });
|
|
598
|
+
// Fire-and-forget: save this slug to installed_skills memory
|
|
599
|
+
const memories = await loadAgentMemory().catch(() => []);
|
|
600
|
+
const existing = extractInstalledSlugs(memories);
|
|
601
|
+
if (!existing.includes(args.slug)) {
|
|
602
|
+
saveMemoryAsync("installed_skills", [...existing, args.slug], "install");
|
|
603
|
+
}
|
|
435
604
|
return JSON.stringify(result, null, 2);
|
|
436
605
|
}
|
|
437
606
|
async function handleListCategories() {
|
|
@@ -439,9 +608,27 @@ async function handleListCategories() {
|
|
|
439
608
|
return JSON.stringify(result, null, 2);
|
|
440
609
|
}
|
|
441
610
|
async function handleRecommendSkills(args) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
611
|
+
// Auto-load agent memory for personalization
|
|
612
|
+
const memories = await loadAgentMemory();
|
|
613
|
+
const installedSlugs = extractInstalledSlugs(memories);
|
|
614
|
+
const recentSearches = extractRecentSearches(memories);
|
|
615
|
+
// Merge installed skills from memory with any explicitly provided
|
|
616
|
+
const allInstalled = new Set(installedSlugs);
|
|
617
|
+
if (args.installed) {
|
|
618
|
+
for (const s of args.installed.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
619
|
+
allInstalled.add(s);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
// Enrich context with relevant search history keywords
|
|
623
|
+
let enrichedContext = args.context;
|
|
624
|
+
if (recentSearches.length > 0) {
|
|
625
|
+
const recentKeywords = recentSearches.slice(-5).join(", ");
|
|
626
|
+
enrichedContext = `${args.context} (recent interests: ${recentKeywords})`;
|
|
627
|
+
}
|
|
628
|
+
const params = new URLSearchParams({ context: enrichedContext });
|
|
629
|
+
if (allInstalled.size > 0) {
|
|
630
|
+
params.set("installed", Array.from(allInstalled).join(","));
|
|
631
|
+
}
|
|
445
632
|
const result = await fetchJSON(`${API_BASE}/recommend?${params.toString()}`, { "X-Agent-Key": AGENT_KEY });
|
|
446
633
|
return JSON.stringify(result, null, 2);
|
|
447
634
|
}
|
|
@@ -469,13 +656,64 @@ async function handleReportSkillUsage(args) {
|
|
|
469
656
|
const body = {
|
|
470
657
|
slug: args.slug,
|
|
471
658
|
status: args.status,
|
|
659
|
+
agent: "claude-code",
|
|
660
|
+
agent_key: AGENT_KEY,
|
|
472
661
|
};
|
|
473
662
|
if (args.error_message) {
|
|
474
663
|
body.error_message = args.error_message;
|
|
475
664
|
}
|
|
476
|
-
const result = await postJSON(`${API_BASE}/report`, body);
|
|
665
|
+
const result = (await postJSON(`${API_BASE}/report`, body));
|
|
666
|
+
if (result.proof) {
|
|
667
|
+
const lines = [
|
|
668
|
+
"Skill usage reported successfully.",
|
|
669
|
+
"",
|
|
670
|
+
"Execution Proof:",
|
|
671
|
+
` Proof ID: ${result.proof.proof_id}`,
|
|
672
|
+
` Verify: ${result.proof.verify_url}`,
|
|
673
|
+
` ${result.proof.shareable_text}`,
|
|
674
|
+
];
|
|
675
|
+
return lines.join("\n");
|
|
676
|
+
}
|
|
477
677
|
return JSON.stringify(result, null, 2);
|
|
478
678
|
}
|
|
679
|
+
async function handleListMyProofs() {
|
|
680
|
+
const params = new URLSearchParams({ agent_key: AGENT_KEY });
|
|
681
|
+
const result = (await fetchJSON(`${API_BASE}/proofs?${params.toString()}`));
|
|
682
|
+
if (result.error) {
|
|
683
|
+
return `Failed to list proofs: ${result.error}`;
|
|
684
|
+
}
|
|
685
|
+
const proofs = result.proofs ?? [];
|
|
686
|
+
if (proofs.length === 0) {
|
|
687
|
+
return "No execution proofs yet. Report successful skill usage to earn proofs.";
|
|
688
|
+
}
|
|
689
|
+
const lines = [
|
|
690
|
+
`Execution Proofs (${proofs.length} total):`,
|
|
691
|
+
"",
|
|
692
|
+
];
|
|
693
|
+
proofs.forEach((p, i) => {
|
|
694
|
+
lines.push(`${i + 1}. ${p.skill_name ?? p.skill_slug} [${p.proof_id}]`);
|
|
695
|
+
lines.push(` Skill: ${p.skill_slug}`);
|
|
696
|
+
lines.push(` Verify: ${p.verify_url}`);
|
|
697
|
+
lines.push(` Date: ${p.created_at ?? "unknown"}`);
|
|
698
|
+
lines.push("");
|
|
699
|
+
});
|
|
700
|
+
return lines.join("\n");
|
|
701
|
+
}
|
|
702
|
+
async function handleVerifyProof(args) {
|
|
703
|
+
const result = (await fetchJSON(`${API_BASE}/verify/${encodeURIComponent(args.proof_id)}`));
|
|
704
|
+
if (result.error) {
|
|
705
|
+
return `Verification failed: ${result.error}`;
|
|
706
|
+
}
|
|
707
|
+
if (!result.valid) {
|
|
708
|
+
return `Proof ${args.proof_id} is NOT valid.`;
|
|
709
|
+
}
|
|
710
|
+
return [
|
|
711
|
+
`Proof ${result.proof_id} is VALID.`,
|
|
712
|
+
` Skill: ${result.skill}`,
|
|
713
|
+
` Verified at: ${result.verified_at}`,
|
|
714
|
+
` Platform: ${result.platform}`,
|
|
715
|
+
].join("\n");
|
|
716
|
+
}
|
|
479
717
|
async function handleSaveMemory(args) {
|
|
480
718
|
const body = {
|
|
481
719
|
agent_key: AGENT_KEY,
|
|
@@ -554,6 +792,83 @@ async function handleInstallPack(args) {
|
|
|
554
792
|
});
|
|
555
793
|
return lines.join("\n");
|
|
556
794
|
}
|
|
795
|
+
async function handleSmartSearch(args) {
|
|
796
|
+
// Load all agent memory (installed skills, recent searches, preferences)
|
|
797
|
+
const memories = await loadAgentMemory();
|
|
798
|
+
const installedSlugs = extractInstalledSlugs(memories);
|
|
799
|
+
const recentSearches = extractRecentSearches(memories);
|
|
800
|
+
const params = new URLSearchParams({ q: args.query });
|
|
801
|
+
if (args.limit)
|
|
802
|
+
params.set("limit", String(args.limit));
|
|
803
|
+
const result = await fetchJSON(`${API_BASE}/search?${params.toString()}`, {
|
|
804
|
+
"X-Agent-Key": AGENT_KEY,
|
|
805
|
+
});
|
|
806
|
+
// Fire-and-forget: record this search query
|
|
807
|
+
recordSearchQuery(args.query, recentSearches);
|
|
808
|
+
// Filter out already-installed skills
|
|
809
|
+
const parsed = result;
|
|
810
|
+
const excludedCount = { value: 0 };
|
|
811
|
+
if (parsed.results && installedSlugs.length > 0) {
|
|
812
|
+
const installedSet = new Set(installedSlugs);
|
|
813
|
+
const originalLength = parsed.results.length;
|
|
814
|
+
parsed.results = parsed.results.filter((r) => !r.slug || !installedSet.has(r.slug));
|
|
815
|
+
excludedCount.value = originalLength - parsed.results.length;
|
|
816
|
+
}
|
|
817
|
+
// Add metadata about personalization
|
|
818
|
+
const output = { ...parsed };
|
|
819
|
+
output._personalization = {
|
|
820
|
+
installed_skills_excluded: excludedCount.value,
|
|
821
|
+
installed_skills_count: installedSlugs.length,
|
|
822
|
+
recent_searches: recentSearches.slice(-5),
|
|
823
|
+
};
|
|
824
|
+
return JSON.stringify(output, null, 2);
|
|
825
|
+
}
|
|
826
|
+
async function handleFlagSkill(args) {
|
|
827
|
+
const body = {
|
|
828
|
+
reason: args.reason,
|
|
829
|
+
};
|
|
830
|
+
if (args.details) {
|
|
831
|
+
body.details = args.details;
|
|
832
|
+
}
|
|
833
|
+
const url = `https://loaditout.ai/api/skills/${encodeURIComponent(args.slug)}/flag`;
|
|
834
|
+
const result = (await postJSON(url, body, { "X-Agent-Key": AGENT_KEY }));
|
|
835
|
+
if (result.error) {
|
|
836
|
+
return `Flag failed: ${result.error}`;
|
|
837
|
+
}
|
|
838
|
+
return `Skill "${args.slug}" has been flagged for "${args.reason}". Thank you for helping keep the registry safe.`;
|
|
839
|
+
}
|
|
840
|
+
async function handleValidateAction(args) {
|
|
841
|
+
const body = {
|
|
842
|
+
slug: args.slug,
|
|
843
|
+
action: args.action,
|
|
844
|
+
agent_key: AGENT_KEY,
|
|
845
|
+
};
|
|
846
|
+
if (args.parameters) {
|
|
847
|
+
body.parameters = args.parameters;
|
|
848
|
+
}
|
|
849
|
+
const result = (await postJSON(`${API_BASE}/validate`, body));
|
|
850
|
+
if (result.error) {
|
|
851
|
+
return `Validation failed: ${result.error}`;
|
|
852
|
+
}
|
|
853
|
+
const lines = [
|
|
854
|
+
`Validation result for ${args.slug} / ${args.action}:`,
|
|
855
|
+
` Safe to proceed: ${result.valid ? "YES" : "NO"}`,
|
|
856
|
+
` Risk level: ${result.risk_level ?? "unknown"}`,
|
|
857
|
+
` Security grade: ${result.security_grade ?? "unknown"}`,
|
|
858
|
+
` Skill verified: ${result.skill_verified ? "yes" : "no"}`,
|
|
859
|
+
];
|
|
860
|
+
if (result.last_updated_days_ago !== undefined) {
|
|
861
|
+
lines.push(` Last updated: ${result.last_updated_days_ago} days ago`);
|
|
862
|
+
}
|
|
863
|
+
const warnings = result.warnings ?? [];
|
|
864
|
+
if (warnings.length > 0) {
|
|
865
|
+
lines.push(` Warnings:`);
|
|
866
|
+
for (const w of warnings) {
|
|
867
|
+
lines.push(` - ${w}`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return lines.join("\n");
|
|
871
|
+
}
|
|
557
872
|
function makeResponse(id, result) {
|
|
558
873
|
return { jsonrpc: "2.0", id, result };
|
|
559
874
|
}
|
|
@@ -617,6 +932,12 @@ async function handleRequest(request) {
|
|
|
617
932
|
case "report_skill_usage":
|
|
618
933
|
resultText = await handleReportSkillUsage(toolArgs);
|
|
619
934
|
break;
|
|
935
|
+
case "list_my_proofs":
|
|
936
|
+
resultText = await handleListMyProofs();
|
|
937
|
+
break;
|
|
938
|
+
case "verify_proof":
|
|
939
|
+
resultText = await handleVerifyProof(toolArgs);
|
|
940
|
+
break;
|
|
620
941
|
case "save_memory":
|
|
621
942
|
resultText = await handleSaveMemory(toolArgs);
|
|
622
943
|
break;
|
|
@@ -638,6 +959,15 @@ async function handleRequest(request) {
|
|
|
638
959
|
case "install_pack":
|
|
639
960
|
resultText = await handleInstallPack(toolArgs);
|
|
640
961
|
break;
|
|
962
|
+
case "validate_action":
|
|
963
|
+
resultText = await handleValidateAction(toolArgs);
|
|
964
|
+
break;
|
|
965
|
+
case "flag_skill":
|
|
966
|
+
resultText = await handleFlagSkill(toolArgs);
|
|
967
|
+
break;
|
|
968
|
+
case "smart_search":
|
|
969
|
+
resultText = await handleSmartSearch(toolArgs);
|
|
970
|
+
break;
|
|
641
971
|
default:
|
|
642
972
|
send(makeError(id, -32601, `Unknown tool: ${toolName}`));
|
|
643
973
|
return;
|