thumbgate 1.8.0 → 1.10.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.
@@ -0,0 +1,110 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_EVIDENCE_TYPES = ['screenshots', 'pdf_pages', 'proof_artifacts'];
4
+ const DEFAULT_DIMS = [1024, 512, 256, 128, 64];
5
+
6
+ function clampInteger(value, { min, max, fallback }) {
7
+ const parsed = Number(value);
8
+ if (!Number.isFinite(parsed)) return fallback;
9
+ return Math.max(min, Math.min(max, Math.floor(parsed)));
10
+ }
11
+
12
+ function normalizeEvidenceTypes(value) {
13
+ if (!Array.isArray(value)) return DEFAULT_EVIDENCE_TYPES;
14
+ const normalized = value
15
+ .map((item) => String(item || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '_'))
16
+ .filter(Boolean);
17
+ return normalized.length > 0 ? [...new Set(normalized)] : DEFAULT_EVIDENCE_TYPES;
18
+ }
19
+
20
+ function dimensionPlan({ corpusItems, maxEmbeddingDim }) {
21
+ const dims = DEFAULT_DIMS.filter((dim) => dim <= maxEmbeddingDim);
22
+ const selected = dims.length > 0 ? dims : [maxEmbeddingDim];
23
+ return selected.map((dim) => ({
24
+ dim,
25
+ estimatedFloat32Mb: Number(((corpusItems * dim * 4) / (1024 * 1024)).toFixed(2)),
26
+ useWhen: dim >= 1024
27
+ ? 'default quality pass for launch-critical retrieval'
28
+ : 'cost-down pass when storage or latency dominates',
29
+ }));
30
+ }
31
+
32
+ function buildMultimodalRetrievalPlan(args = {}) {
33
+ const evidenceTypes = normalizeEvidenceTypes(args.evidenceTypes);
34
+ const corpusItems = clampInteger(args.corpusItems, {
35
+ min: 100,
36
+ max: 10000000,
37
+ fallback: 5000,
38
+ });
39
+ const maxEmbeddingDim = clampInteger(args.maxEmbeddingDim, {
40
+ min: 64,
41
+ max: 2048,
42
+ fallback: 1024,
43
+ });
44
+ const latencyBudgetMs = clampInteger(args.latencyBudgetMs, {
45
+ min: 50,
46
+ max: 30000,
47
+ fallback: 750,
48
+ });
49
+ const useReranker = args.useReranker !== false;
50
+ const goal = String(args.goal || 'retrieve visual proof for agent-governance decisions').trim();
51
+ const dims = dimensionPlan({ corpusItems, maxEmbeddingDim });
52
+ const defaultDim = dims.some((entry) => entry.dim === 1024) ? 1024 : dims[0].dim;
53
+
54
+ return {
55
+ planVersion: '2026-04-20',
56
+ sourcePattern: 'multimodal Sentence Transformers visual document retrieval',
57
+ goal,
58
+ evidenceTypes,
59
+ architecture: {
60
+ stage1: 'Index screenshots, PDF pages, dashboard captures, and proof artifacts with a multimodal embedding model.',
61
+ stage2: useReranker
62
+ ? 'Rerank the top candidates with a multimodal cross-encoder before using evidence in a gate, PR, or sales proof claim.'
63
+ : 'Skip reranking for low-latency agent recall; require stronger holdout evaluation before shipping.',
64
+ fallback: 'Keep text-only search as a fallback for code, logs, markdown, and plain policy docs.',
65
+ },
66
+ trainingData: {
67
+ pilotSchema: ['query', 'image', 'negative_0'],
68
+ hardNegativeStrategy: 'Pair each proof query with visually similar but wrong screenshots or PDF pages.',
69
+ minimumPilot: 'Start with 300 labeled evaluation queries and at least one hard negative per query before finetuning.',
70
+ },
71
+ evaluation: {
72
+ baseline: 'Measure current text-only retrieval before any model changes.',
73
+ primaryMetric: 'NDCG@10',
74
+ secondaryMetrics: ['Recall@5', 'MAP', 'false_positive_gate_rate'],
75
+ holdoutSets: [
76
+ 'agent failure screenshots',
77
+ 'dashboard proof captures',
78
+ 'visual docs that contain tables or charts',
79
+ ],
80
+ },
81
+ deployment: {
82
+ latencyBudgetMs,
83
+ defaultEmbeddingDim: defaultDim,
84
+ matryoshkaDimensions: dims,
85
+ compressionPath: 'Use Matryoshka truncation first, then quantization only after holdout quality is stable.',
86
+ },
87
+ thumbgateUseCases: [
88
+ 'Find the exact screenshot or proof artifact behind a completion claim.',
89
+ 'Retrieve visual evidence before approving a workflow-hardening sprint.',
90
+ 'Rank dashboard captures and PDF runbook pages for GEO/SEO evidence pages.',
91
+ 'Attach visual hard negatives to Autoresearch loops so agents cannot reward-hack by deleting hard cases.',
92
+ ],
93
+ guardrails: [
94
+ 'Never promote visual retrieval results into claims without a linked artifact URL or local path.',
95
+ 'Keep the multimodal index read-only for agent recall; gate training and index rebuilds behind explicit workflow checks.',
96
+ 'Evaluate retrieval on holdout screenshots/PDF pages before replacing text-only recall.',
97
+ ],
98
+ nextActions: [
99
+ 'Create a small visual proof corpus from existing public dashboard screenshots and proof artifacts.',
100
+ 'Log query -> correct artifact -> hard negative triples during workflow sprint reviews.',
101
+ 'Use Autoresearch to optimize NDCG@10 and latency only after the baseline corpus exists.',
102
+ ],
103
+ };
104
+ }
105
+
106
+ module.exports = {
107
+ buildMultimodalRetrievalPlan,
108
+ dimensionPlan,
109
+ normalizeEvidenceTypes,
110
+ };
@@ -324,6 +324,48 @@ function samplePosteriors(model) {
324
324
  return samples;
325
325
  }
326
326
 
327
+ /**
328
+ * Production/exploit-mode counterpart to `samplePosteriors`. Instead of
329
+ * drawing a random sample from each Beta posterior (which deliberately
330
+ * explores), return the posterior *mean* α/(α+β) for each category. Picking
331
+ * argmax over these means is the Bayes-optimal action under 0-1 loss when
332
+ * we only care about expected reward and do not need exploration.
333
+ *
334
+ * When to use which:
335
+ * - `samplePosteriors` in training / learning mode — we want to try
336
+ * under-sampled arms.
337
+ * - `argmaxPosteriors` in production / hot-path mode — we want the
338
+ * best-known lesson right now. The caller can still choose to mix the
339
+ * two (e.g. ε-greedy) but that's out of scope here.
340
+ */
341
+ function argmaxPosteriors(model) {
342
+ const means = {};
343
+ for (const [cat, params] of Object.entries(model.categories || {})) {
344
+ const alpha = Math.max(Number(params.alpha) || 0, 0.01);
345
+ const beta = Math.max(Number(params.beta) || 0, 0.01);
346
+ means[cat] = alpha / (alpha + beta);
347
+ }
348
+ return means;
349
+ }
350
+
351
+ /**
352
+ * Pick the single category with the highest posterior mean. Ties broken by
353
+ * lexicographic order for determinism. Returns `null` when no categories
354
+ * are present.
355
+ */
356
+ function pickBestCategory(model) {
357
+ const means = argmaxPosteriors(model);
358
+ const keys = Object.keys(means);
359
+ if (keys.length === 0) return null;
360
+ // Use localeCompare for deterministic, locale-aware alphabetical tie-break.
361
+ keys.sort((a, b) => a.localeCompare(b));
362
+ let best = keys[0];
363
+ for (const key of keys) {
364
+ if (means[key] > means[best]) best = key;
365
+ }
366
+ return best;
367
+ }
368
+
327
369
  // ---------------------------------------------------------------------------
328
370
  // Internal: Marsaglia-Tsang Gamma Sampling (2000)
329
371
  // ---------------------------------------------------------------------------
@@ -410,6 +452,8 @@ module.exports = {
410
452
  isCalibrated,
411
453
  getCalibration,
412
454
  samplePosteriors,
455
+ argmaxPosteriors,
456
+ pickBestCategory,
413
457
  HALF_LIFE_DAYS,
414
458
  DECAY_FLOOR,
415
459
  MIN_SAMPLES_THRESHOLD,
@@ -134,6 +134,25 @@ const TOOLS = [
134
134
  },
135
135
  },
136
136
  }),
137
+ readOnlyTool({
138
+ name: 'plan_multimodal_retrieval',
139
+ description: 'Plan a high-ROI multimodal retrieval rollout for screenshots, PDF pages, dashboard captures, and proof artifacts without starting GPU training.',
140
+ inputSchema: {
141
+ type: 'object',
142
+ properties: {
143
+ goal: { type: 'string', description: 'Business or workflow objective for visual/document retrieval.' },
144
+ evidenceTypes: {
145
+ type: 'array',
146
+ items: { type: 'string' },
147
+ description: 'Evidence surfaces to include, such as screenshots, pdf_pages, proof_artifacts, dashboards, or videos.',
148
+ },
149
+ corpusItems: { type: 'number', description: 'Estimated number of visual artifacts or document pages to index.' },
150
+ maxEmbeddingDim: { type: 'number', description: 'Maximum embedding dimension to budget for Matryoshka-style truncation planning.' },
151
+ latencyBudgetMs: { type: 'number', description: 'Target retrieval latency budget for agent recall.' },
152
+ useReranker: { type: 'boolean', description: 'Whether to include a multimodal reranker stage after initial embedding retrieval.' },
153
+ },
154
+ },
155
+ }),
137
156
  destructiveTool({
138
157
  name: 'import_document',
139
158
  description: 'Import a local policy or runbook document into ThumbGate, normalize it for search, and propose provenance-backed gate candidates.',
@@ -872,6 +891,24 @@ const TOOLS = [
872
891
  },
873
892
  },
874
893
  }),
894
+ destructiveTool({
895
+ name: 'run_autoresearch',
896
+ description: 'Run a bounded metric-improvement loop: measure a baseline, test a hypothesis, require primary and holdout checks, then keep or discard the candidate mutation with proof.',
897
+ inputSchema: {
898
+ type: 'object',
899
+ properties: {
900
+ iterations: { type: 'number', description: 'Number of iterations to run. Capped at 5 per call; default 1.' },
901
+ targetName: { type: 'string', enum: ['half_life_days', 'decay_floor', 'prevention_min_occurrences', 'verification_max_retries', 'dpo_beta'], description: 'Optional evolution target to mutate.' },
902
+ nextValue: { type: 'number', description: 'Optional explicit candidate value for the target.' },
903
+ testCommand: { type: 'string', description: 'Primary metric command. Defaults to npm test.' },
904
+ holdoutCommands: { type: 'array', items: { type: 'string' }, description: 'Additional checks required before a candidate can be kept.' },
905
+ timeoutMs: { type: 'number', description: 'Per-command timeout in milliseconds. Capped at 600000; default 120000.' },
906
+ cwd: { type: 'string', description: 'Optional workspace directory for the evaluation commands.' },
907
+ researchQuery: { type: 'string', description: 'Optional research query used to build an autoresearch context brief.' },
908
+ paperLimit: { type: 'number', description: 'Maximum research papers to ingest when researchQuery is set. Capped at 10; default 5.' },
909
+ },
910
+ },
911
+ }),
875
912
  destructiveTool({
876
913
  name: 'schedule',
877
914
  description: 'Create, list, or delete scheduled tasks. Supports natural language scheduling like "daily 9:00", "weekly monday 8:30", "hourly". Installs as macOS LaunchAgent or Linux crontab.',
package/src/api/server.js CHANGED
@@ -552,6 +552,169 @@ function getServerCardTools() {
552
552
  }));
553
553
  }
554
554
 
555
+ function buildPublicUrl(hostedConfig, pathname) {
556
+ return `${hostedConfig.appOrigin}${pathname}`;
557
+ }
558
+
559
+ const VERIFICATION_EVIDENCE_URL = 'https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/VERIFICATION_EVIDENCE.md';
560
+
561
+ function getToolDiscoveryIndex(hostedConfig) {
562
+ return MCP_TOOLS.map((tool) => ({
563
+ name: tool.name,
564
+ description: tool.description,
565
+ annotations: tool.annotations || {},
566
+ schemaUrl: buildPublicUrl(hostedConfig, `/.well-known/mcp/tools/${encodeURIComponent(tool.name)}.json`),
567
+ }));
568
+ }
569
+
570
+ function getMcpSkillManifests(hostedConfig) {
571
+ return [
572
+ {
573
+ name: 'thumbgate',
574
+ title: 'ThumbGate Pre-Action Gates',
575
+ description: 'Capture feedback, recall lessons, generate rules, and block repeated agent mistakes before tool execution.',
576
+ triggers: ['thumbgate', 'pre-action gates', 'prevent repeated AI mistakes', 'agent feedback', 'PreToolUse hooks'],
577
+ recommendedFlow: [
578
+ 'Recall lessons before risky work.',
579
+ 'Plan high-risk actions with checkpoints.',
580
+ 'Capture concrete thumbs-down/up feedback.',
581
+ 'Inspect prevention_rules after repeats.',
582
+ ],
583
+ installCommand: 'npx thumbgate init',
584
+ contextUrl: buildPublicUrl(hostedConfig, '/public/llm-context.md'),
585
+ proofUrl: VERIFICATION_EVIDENCE_URL,
586
+ },
587
+ {
588
+ name: 'workflow-hardening-sprint',
589
+ title: 'Workflow Hardening Sprint',
590
+ description: 'Turn one repeated agent failure into an enforced gate with proof and rollout evidence.',
591
+ triggers: ['workflow hardening', 'team rollout', 'agent governance', 'approval boundary', 'audit trail'],
592
+ recommendedFlow: [
593
+ 'Pick one costly repeated failure.',
594
+ 'Import the policy or runbook.',
595
+ 'Ship the gate with dashboard proof.',
596
+ ],
597
+ intakeUrl: buildPublicUrl(hostedConfig, '/#workflow-sprint-intake'),
598
+ proofUrl: VERIFICATION_EVIDENCE_URL,
599
+ },
600
+ {
601
+ name: 'visual-proof-retrieval',
602
+ title: 'Visual Proof Retrieval',
603
+ description: 'Use screenshots, PDF pages, dashboard captures, and proof artifacts as searchable evidence for agent-governance claims.',
604
+ triggers: ['visual document retrieval', 'multimodal embeddings', 'screenshots', 'PDF evidence', 'proof artifacts'],
605
+ recommendedFlow: [
606
+ 'Plan the corpus and Matryoshka dimension budget.',
607
+ 'Baseline text-only retrieval before finetuning.',
608
+ 'Evaluate NDCG@10 on visual hard negatives.',
609
+ 'Require artifact links before using retrieved evidence in claims.',
610
+ ],
611
+ contextUrl: buildPublicUrl(hostedConfig, '/public/llm-context.md'),
612
+ proofUrl: VERIFICATION_EVIDENCE_URL,
613
+ },
614
+ ];
615
+ }
616
+
617
+ function getMcpApplications(hostedConfig) {
618
+ return [
619
+ {
620
+ name: 'dashboard',
621
+ title: 'ThumbGate Dashboard',
622
+ description: 'Review feedback, gates, blocked actions, funnel metrics, and proof.',
623
+ url: buildPublicUrl(hostedConfig, '/dashboard'),
624
+ useWhen: 'Need proof before approving more autonomy.',
625
+ },
626
+ {
627
+ name: 'lessons',
628
+ title: 'Lessons',
629
+ description: 'Browse promoted lessons and corrective actions.',
630
+ url: buildPublicUrl(hostedConfig, '/lessons'),
631
+ useWhen: 'Need human-approved context before risk.',
632
+ },
633
+ {
634
+ name: 'guide',
635
+ title: 'Setup Guide',
636
+ description: 'Install ThumbGate for Claude Code, Cursor, Codex, Gemini CLI, Amp, OpenCode, and MCP agents.',
637
+ url: buildPublicUrl(hostedConfig, '/guide'),
638
+ useWhen: 'Need setup without searching the repo.',
639
+ },
640
+ {
641
+ name: 'workflow-sprint-intake',
642
+ title: 'Workflow Hardening Sprint Intake',
643
+ description: 'Submit a repeated agent failure for a proof-backed sprint.',
644
+ url: buildPublicUrl(hostedConfig, '/#workflow-sprint-intake'),
645
+ useWhen: 'Ready to convert mistakes into gates.',
646
+ },
647
+ ];
648
+ }
649
+
650
+ function getMcpDiscoveryManifest(hostedConfig) {
651
+ return {
652
+ schemaVersion: '2026-04-20',
653
+ name: 'thumbgate',
654
+ title: 'ThumbGate',
655
+ version: pkg.version,
656
+ description: 'Pre-Action Gates for AI coding agents: feedback, recall, prevention rules, and tool-call blocking.',
657
+ homepage: hostedConfig.appOrigin,
658
+ repository: 'https://github.com/IgorGanapolsky/ThumbGate',
659
+ package: {
660
+ registry: 'npm',
661
+ name: 'thumbgate',
662
+ installCommand: 'npx thumbgate init',
663
+ },
664
+ transport: {
665
+ type: 'streamable-http',
666
+ endpoint: buildPublicUrl(hostedConfig, '/mcp'),
667
+ unauthenticatedDiscovery: ['initialize', 'tools/list'],
668
+ authenticatedMethods: ['tools/call'],
669
+ },
670
+ discovery: {
671
+ serverCardUrl: buildPublicUrl(hostedConfig, '/.well-known/mcp/server-card.json'),
672
+ toolIndexUrl: buildPublicUrl(hostedConfig, '/.well-known/mcp/tools.json'),
673
+ toolSchemaUrlTemplate: buildPublicUrl(hostedConfig, '/.well-known/mcp/tools/{name}.json'),
674
+ skillsUrl: buildPublicUrl(hostedConfig, '/.well-known/mcp/skills.json'),
675
+ applicationsUrl: buildPublicUrl(hostedConfig, '/.well-known/mcp/applications.json'),
676
+ llmsTxtUrl: buildPublicUrl(hostedConfig, '/.well-known/llms.txt'),
677
+ progressive: {
678
+ pattern: 'Load manifest, inspect tools.json, fetch one tool schema only when needed.',
679
+ tokenStrategy: 'Do not preload every inputSchema. Use per-tool schema URLs.',
680
+ },
681
+ },
682
+ primaryFlows: [
683
+ {
684
+ name: 'capture-to-gate',
685
+ description: 'Capture feedback, retrieve lessons, generate rules, enforce a gate.',
686
+ tools: ['capture_feedback', 'search_lessons', 'prevention_rules', 'gate_stats'],
687
+ },
688
+ {
689
+ name: 'safe-autonomous-work',
690
+ description: 'Plan high-risk work, recall lessons, diagnose failures.',
691
+ tools: ['plan_intent', 'recall', 'diagnose_failure', 'feedback_summary'],
692
+ },
693
+ {
694
+ name: 'team-rollout-proof',
695
+ description: 'Show dashboard evidence, metrics, and sprint proof.',
696
+ tools: ['dashboard', 'get_business_metrics', 'construct_context_pack'],
697
+ },
698
+ {
699
+ name: 'metric-autoresearch',
700
+ description: 'Run bounded baseline -> hypothesis -> holdout loops with keep/discard proof.',
701
+ tools: ['get_business_metrics', 'construct_context_pack', 'run_autoresearch', 'require_evidence_for_claim'],
702
+ },
703
+ {
704
+ name: 'visual-proof-retrieval',
705
+ description: 'Plan screenshot/PDF/proof-artifact retrieval before investing in multimodal finetuning.',
706
+ tools: ['plan_multimodal_retrieval', 'search_thumbgate', 'construct_context_pack', 'require_evidence_for_claim'],
707
+ },
708
+ ],
709
+ skills: getMcpSkillManifests(hostedConfig),
710
+ applications: getMcpApplications(hostedConfig),
711
+ proof: {
712
+ verificationEvidenceUrl: VERIFICATION_EVIDENCE_URL,
713
+ llmContextUrl: buildPublicUrl(hostedConfig, '/public/llm-context.md'),
714
+ },
715
+ };
716
+ }
717
+
555
718
  function createHttpError(statusCode, message) {
556
719
  const err = new Error(message);
557
720
  err.statusCode = statusCode;
@@ -3904,7 +4067,85 @@ async function addContext(){
3904
4067
  return;
3905
4068
  }
3906
4069
 
4070
+ if (isGetLikeRequest && pathname === '/.well-known/mcp.json') {
4071
+ sendJson(res, 200, getMcpDiscoveryManifest(hostedConfig), {}, {
4072
+ headOnly: isHeadRequest,
4073
+ });
4074
+ return;
4075
+ }
4076
+
4077
+ if (isGetLikeRequest && pathname === '/.well-known/mcp/tools.json') {
4078
+ sendJson(res, 200, {
4079
+ name: 'thumbgate',
4080
+ version: pkg.version,
4081
+ count: MCP_TOOLS.length,
4082
+ tools: getToolDiscoveryIndex(hostedConfig),
4083
+ }, {}, {
4084
+ headOnly: isHeadRequest,
4085
+ });
4086
+ return;
4087
+ }
4088
+
4089
+ if (isGetLikeRequest && pathname.startsWith('/.well-known/mcp/tools/') && pathname.endsWith('.json')) {
4090
+ const encodedToolName = pathname.slice('/.well-known/mcp/tools/'.length, -'.json'.length);
4091
+ let toolName = encodedToolName;
4092
+ try {
4093
+ toolName = decodeURIComponent(encodedToolName);
4094
+ } catch (_err) {
4095
+ sendJson(res, 400, {
4096
+ error: 'invalid_tool_name',
4097
+ toolIndexUrl: buildPublicUrl(hostedConfig, '/.well-known/mcp/tools.json'),
4098
+ }, {}, {
4099
+ headOnly: isHeadRequest,
4100
+ });
4101
+ return;
4102
+ }
4103
+ const tool = MCP_TOOLS.find((candidate) => candidate.name === toolName);
4104
+ if (!tool) {
4105
+ sendJson(res, 404, {
4106
+ error: 'tool_not_found',
4107
+ toolName,
4108
+ toolIndexUrl: buildPublicUrl(hostedConfig, '/.well-known/mcp/tools.json'),
4109
+ }, {}, {
4110
+ headOnly: isHeadRequest,
4111
+ });
4112
+ return;
4113
+ }
4114
+ sendJson(res, 200, {
4115
+ name: tool.name,
4116
+ description: tool.description,
4117
+ annotations: tool.annotations || {},
4118
+ inputSchema: tool.inputSchema,
4119
+ }, {}, {
4120
+ headOnly: isHeadRequest,
4121
+ });
4122
+ return;
4123
+ }
4124
+
4125
+ if (isGetLikeRequest && pathname === '/.well-known/mcp/skills.json') {
4126
+ sendJson(res, 200, {
4127
+ name: 'thumbgate',
4128
+ version: pkg.version,
4129
+ skills: getMcpSkillManifests(hostedConfig),
4130
+ }, {}, {
4131
+ headOnly: isHeadRequest,
4132
+ });
4133
+ return;
4134
+ }
4135
+
4136
+ if (isGetLikeRequest && pathname === '/.well-known/mcp/applications.json') {
4137
+ sendJson(res, 200, {
4138
+ name: 'thumbgate',
4139
+ version: pkg.version,
4140
+ applications: getMcpApplications(hostedConfig),
4141
+ }, {}, {
4142
+ headOnly: isHeadRequest,
4143
+ });
4144
+ return;
4145
+ }
4146
+
3907
4147
  if (isGetLikeRequest && pathname === '/.well-known/mcp/server-card.json') {
4148
+ const discoveryManifest = getMcpDiscoveryManifest(hostedConfig);
3908
4149
  sendJson(res, 200, {
3909
4150
  serverInfo: {
3910
4151
  name: 'thumbgate',
@@ -3913,7 +4154,12 @@ async function addContext(){
3913
4154
  name: 'thumbgate',
3914
4155
  description: 'Pre-action gates that physically block AI coding agents from repeating known mistakes. Captures feedback, auto-promotes failures into prevention rules, and enforces them via PreToolUse hooks. Works with Claude Code, Codex, Gemini, Amp, Cursor, OpenCode, and any MCP-compatible agent.',
3915
4156
  version: pkg.version,
4157
+ transport: discoveryManifest.transport,
4158
+ discovery: discoveryManifest.discovery,
3916
4159
  tools: getServerCardTools(),
4160
+ skills: getMcpSkillManifests(hostedConfig),
4161
+ applications: getMcpApplications(hostedConfig),
4162
+ proof: discoveryManifest.proof,
3917
4163
  repository: 'https://github.com/IgorGanapolsky/ThumbGate',
3918
4164
  homepage: hostedConfig.appOrigin,
3919
4165
  }, {}, {