tokrepo-mcp-server 2.5.0 → 2.7.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/README.md +16 -3
- package/bin/server.js +360 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,17 +35,20 @@ gemini settings mcp add tokrepo -- npx -y tokrepo-mcp-server
|
|
|
35
35
|
|
|
36
36
|
Once connected, your AI assistant can:
|
|
37
37
|
|
|
38
|
-
- **
|
|
38
|
+
- **Discover during planning** — turn a task or capability gap into structured candidate skills, prompts, MCP configs, scripts, and workflows
|
|
39
|
+
- **Search** 200+ curated AI assets by keyword or category with agent fit signals
|
|
39
40
|
- **Browse** trending assets, filter by type (MCP, Skill, Prompt, Agent, Script)
|
|
40
41
|
- **Get details** — full documentation, install instructions, and metadata
|
|
41
42
|
- **Plan before install** — get install plan v2 with policy decisions, rollback, and verification
|
|
42
43
|
- **Safe Codex install** — dry-run by default; risky assets must be staged or explicitly approved
|
|
43
44
|
- **Lifecycle control** — list, update, uninstall, and roll back managed Codex installs
|
|
45
|
+
- **Project memory** — pair with `tokrepo init-agent --target all` so future agents know to call TokRepo during planning
|
|
44
46
|
|
|
45
47
|
## Available Tools
|
|
46
48
|
|
|
47
49
|
| Tool | Description |
|
|
48
50
|
|------|-------------|
|
|
51
|
+
| `tokrepo_discover` | Planning-time capability discovery from a task, environment, and constraints |
|
|
49
52
|
| `tokrepo_search` | Search assets by keyword/tag with `agent_fit` ranking |
|
|
50
53
|
| `tokrepo_detail` | Get full asset details by UUID |
|
|
51
54
|
| `tokrepo_install_plan` | Get agent-native install plan v2 |
|
|
@@ -60,10 +63,10 @@ Once connected, your AI assistant can:
|
|
|
60
63
|
|
|
61
64
|
```
|
|
62
65
|
You: "Find me a good MCP server for databases"
|
|
63
|
-
AI: [calls
|
|
66
|
+
AI: [calls tokrepo_discover] → Ranks DBHub, Supabase MCP, PostgreSQL MCP as candidate capabilities
|
|
64
67
|
|
|
65
68
|
You: "What video assets should I install?"
|
|
66
|
-
AI: [calls
|
|
69
|
+
AI: [calls tokrepo_discover] → Finds relevant skills, checks fit and policy, then asks before installing
|
|
67
70
|
|
|
68
71
|
You: "Install that cursor rules asset"
|
|
69
72
|
AI: [calls tokrepo_install_plan] → Reviews policy and actions
|
|
@@ -71,6 +74,16 @@ AI: [calls tokrepo_codex_install with dry_run=false, confirm=true] → Writes on
|
|
|
71
74
|
AI: [calls tokrepo_rollback with dry_run=true] → Shows exactly what would be removed before rollback
|
|
72
75
|
```
|
|
73
76
|
|
|
77
|
+
## Make Future Agents Discover TokRepo
|
|
78
|
+
|
|
79
|
+
Run this once in a project:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npx tokrepo init-agent --target all
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
It writes managed instructions to `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, Cursor rules, and `.mcp.json`. The rule is simple: during planning, when the agent sees a capability gap, it should call `tokrepo_discover` before inventing a one-off local tool. After a task, agents can run `tokrepo agent-handoff --json` to suggest reusable files for user-confirmed private publishing.
|
|
86
|
+
|
|
74
87
|
## Why TokRepo?
|
|
75
88
|
|
|
76
89
|
TokRepo is the **open registry for AI assets** — like npm for packages, but for AI skills, prompts, MCP configs, and workflows.
|
package/bin/server.js
CHANGED
|
@@ -19,7 +19,7 @@ const API_BASE = process.env.TOKREPO_API || 'https://api.tokrepo.com';
|
|
|
19
19
|
const TOKREPO_URL = 'https://tokrepo.com';
|
|
20
20
|
const TOKREPO_TOKEN = process.env.TOKREPO_TOKEN || '';
|
|
21
21
|
const TOKREPO_CLI = process.env.TOKREPO_CLI || '';
|
|
22
|
-
const SERVER_VERSION = '2.
|
|
22
|
+
const SERVER_VERSION = '2.7.0';
|
|
23
23
|
|
|
24
24
|
// ─── MCP Protocol (JSON-RPC over stdio) ───
|
|
25
25
|
|
|
@@ -33,9 +33,44 @@ const CAPABILITIES = {
|
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
const TOOLS = [
|
|
36
|
+
{
|
|
37
|
+
name: 'tokrepo_discover',
|
|
38
|
+
description: 'Planning-time capability discovery for agents. Call this during autonomous planning when the task may need a reusable skill, MCP config, prompt, script, workflow, or other AI asset. Returns structured candidates, fit signals, and next MCP calls.',
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
task: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: 'The user goal, subtask, or capability gap the agent is planning for.',
|
|
45
|
+
},
|
|
46
|
+
target: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Optional agent/runtime target. Use "any" for generic discovery.',
|
|
49
|
+
enum: ['any', 'codex', 'claude_code', 'gemini_cli', 'cursor', 'windsurf', 'mcp_client'],
|
|
50
|
+
default: 'any',
|
|
51
|
+
},
|
|
52
|
+
environment: {
|
|
53
|
+
type: 'object',
|
|
54
|
+
description: 'Optional local environment signals, such as project_type, frameworks, language, can_run_shell, can_write_files, or browser_available.',
|
|
55
|
+
additionalProperties: true,
|
|
56
|
+
},
|
|
57
|
+
constraints: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
description: 'Optional constraints such as kind, policy, risk, language, prefer_verified, or must_not_modify_files.',
|
|
60
|
+
additionalProperties: true,
|
|
61
|
+
},
|
|
62
|
+
limit: {
|
|
63
|
+
type: 'number',
|
|
64
|
+
description: 'Max candidates (default 6, max 10)',
|
|
65
|
+
default: 6,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
required: ['task'],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
36
71
|
{
|
|
37
72
|
name: 'tokrepo_search',
|
|
38
|
-
description: 'Search TokRepo for AI assets (skills, prompts, MCP configs, scripts, workflows). Returns matching assets with titles, descriptions, tags, stars, and install commands. Use this when the user asks to find AI tools, MCP servers,
|
|
73
|
+
description: 'Search TokRepo for AI assets (skills, prompts, MCP configs, scripts, workflows). Returns matching assets with titles, descriptions, tags, stars, and install commands. Use this when the user asks to find AI tools, MCP servers, skills, prompts, or workflows.',
|
|
39
74
|
inputSchema: {
|
|
40
75
|
type: 'object',
|
|
41
76
|
properties: {
|
|
@@ -55,9 +90,8 @@ const TOOLS = [
|
|
|
55
90
|
},
|
|
56
91
|
target: {
|
|
57
92
|
type: 'string',
|
|
58
|
-
description: '
|
|
59
|
-
enum: ['codex'],
|
|
60
|
-
default: 'codex',
|
|
93
|
+
description: 'Optional agent target filter. Use "any" or omit it for generic discovery.',
|
|
94
|
+
enum: ['any', 'codex', 'claude_code', 'gemini_cli', 'cursor', 'windsurf', 'mcp_client'],
|
|
61
95
|
},
|
|
62
96
|
kind: {
|
|
63
97
|
type: 'string',
|
|
@@ -112,8 +146,8 @@ const TOOLS = [
|
|
|
112
146
|
},
|
|
113
147
|
target: {
|
|
114
148
|
type: 'string',
|
|
115
|
-
description: 'Install target.
|
|
116
|
-
enum: ['codex'],
|
|
149
|
+
description: 'Install target adapter. Codex is native; other adapters may return planned or staged contracts as they become available.',
|
|
150
|
+
enum: ['codex', 'claude_code', 'gemini_cli'],
|
|
117
151
|
default: 'codex',
|
|
118
152
|
},
|
|
119
153
|
},
|
|
@@ -397,6 +431,7 @@ const TOOLS = [
|
|
|
397
431
|
];
|
|
398
432
|
|
|
399
433
|
const EXPOSED_TOOL_NAMES = new Set([
|
|
434
|
+
'tokrepo_discover',
|
|
400
435
|
'tokrepo_search',
|
|
401
436
|
'tokrepo_detail',
|
|
402
437
|
'tokrepo_install_plan',
|
|
@@ -577,13 +612,313 @@ function jsonText(title, data) {
|
|
|
577
612
|
return `${title}\n\n\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\``;
|
|
578
613
|
}
|
|
579
614
|
|
|
615
|
+
function compactText(value, maxLength = 240) {
|
|
616
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
617
|
+
if (text.length <= maxLength) return text;
|
|
618
|
+
return `${text.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function clampLimit(value, fallback, max) {
|
|
622
|
+
const parsed = Number(value);
|
|
623
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
|
|
624
|
+
return Math.min(Math.floor(parsed), max);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function asArray(value) {
|
|
628
|
+
if (Array.isArray(value)) return value.filter(Boolean);
|
|
629
|
+
if (value === undefined || value === null || value === '') return [];
|
|
630
|
+
return [value];
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function normalizeTarget(value) {
|
|
634
|
+
const raw = compactText(value || 'any', 64).toLowerCase().replace(/[-\s]+/g, '_');
|
|
635
|
+
const aliases = {
|
|
636
|
+
claude: 'claude_code',
|
|
637
|
+
claude_code: 'claude_code',
|
|
638
|
+
gemini: 'gemini_cli',
|
|
639
|
+
gemini_cli: 'gemini_cli',
|
|
640
|
+
codex: 'codex',
|
|
641
|
+
cursor: 'cursor',
|
|
642
|
+
windsurf: 'windsurf',
|
|
643
|
+
mcp: 'mcp_client',
|
|
644
|
+
mcp_client: 'mcp_client',
|
|
645
|
+
any: 'any',
|
|
646
|
+
all: 'any',
|
|
647
|
+
};
|
|
648
|
+
return aliases[raw] || raw || 'any';
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function itemTags(item) {
|
|
652
|
+
return asArray(item.tags).map(tag => compactText(tag.name || tag.slug || tag, 64)).filter(Boolean);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function itemAgentMetadata(item) {
|
|
656
|
+
return item.agent_metadata || item.agentMetadata || item.metadata?.agent_metadata || {};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function itemAgentFit(item) {
|
|
660
|
+
return item.agent_fit || item.agentFit || item.fit || {};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function candidateUuid(item) {
|
|
664
|
+
return compactText(item.uuid || item.workflow_uuid || item.workflowUuid || item.id, 128);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function candidateKind(item, metadata, fit) {
|
|
668
|
+
return compactText(item.asset_kind || item.assetKind || metadata.asset_kind || fit.asset_kind || '', 64);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function candidateTargets(item, metadata) {
|
|
672
|
+
const targets = [
|
|
673
|
+
...asArray(item.target_tools || item.targetTools),
|
|
674
|
+
...asArray(metadata.target_tools || metadata.targetTools),
|
|
675
|
+
].map(target => compactText(target, 64)).filter(Boolean);
|
|
676
|
+
return [...new Set(targets)];
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function extractSearchTerms(value, maxTerms = 8) {
|
|
680
|
+
const text = compactText(value, 240);
|
|
681
|
+
const stopWords = new Set([
|
|
682
|
+
'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'can', 'do', 'for', 'from', 'how',
|
|
683
|
+
'i', 'in', 'into', 'is', 'it', 'me', 'my', 'of', 'on', 'or', 'our', 'the', 'this',
|
|
684
|
+
'to', 'use', 'with', 'fix', 'make', 'need', 'needs', 'want', 'issue', 'issues',
|
|
685
|
+
]);
|
|
686
|
+
const words = text
|
|
687
|
+
.split(/[^a-zA-Z0-9+#.]+/)
|
|
688
|
+
.map(word => word.trim().toLowerCase())
|
|
689
|
+
.filter(word => word.length >= 2 && !stopWords.has(word));
|
|
690
|
+
const unique = [...new Set(words)].slice(0, maxTerms);
|
|
691
|
+
return unique.length ? unique : [compactText(text, 80)];
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function buildDiscoveryQuery(task, environment, constraints) {
|
|
695
|
+
const parts = extractSearchTerms(task, 6);
|
|
696
|
+
for (const key of ['project_type', 'language', 'framework', 'domain']) {
|
|
697
|
+
if (environment?.[key]) parts.push(...extractSearchTerms(environment[key], 2));
|
|
698
|
+
}
|
|
699
|
+
for (const value of asArray(environment?.frameworks)) parts.push(...extractSearchTerms(value, 2));
|
|
700
|
+
if (constraints?.kind) parts.push(...extractSearchTerms(constraints.kind, 2));
|
|
701
|
+
return compactText([...new Set(parts)].join(' '), 100);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function scoreDiscoveryCandidate(item, task, target, constraints = {}) {
|
|
705
|
+
const metadata = itemAgentMetadata(item);
|
|
706
|
+
const fit = itemAgentFit(item);
|
|
707
|
+
const tags = itemTags(item);
|
|
708
|
+
const targets = candidateTargets(item, metadata);
|
|
709
|
+
const terms = extractSearchTerms(task, 10);
|
|
710
|
+
const haystack = [
|
|
711
|
+
item.title,
|
|
712
|
+
item.description,
|
|
713
|
+
tags.join(' '),
|
|
714
|
+
metadata.entrypoint,
|
|
715
|
+
metadata.asset_kind,
|
|
716
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
717
|
+
let score = Number.isFinite(Number(fit.score)) ? Number(fit.score) : 45;
|
|
718
|
+
const reasons = [];
|
|
719
|
+
const matched = terms.filter(term => haystack.includes(term));
|
|
720
|
+
if (matched.length) {
|
|
721
|
+
score += Math.min(24, matched.length * 4);
|
|
722
|
+
reasons.push(`task term match: ${matched.slice(0, 5).join(', ')}`);
|
|
723
|
+
}
|
|
724
|
+
if (target && target !== 'any') {
|
|
725
|
+
if (targets.includes(target) || fit.target === target) {
|
|
726
|
+
score += 12;
|
|
727
|
+
reasons.push(`target matches ${target}`);
|
|
728
|
+
} else if (targets.length) {
|
|
729
|
+
score -= 10;
|
|
730
|
+
reasons.push(`target metadata is ${targets.join(', ')}`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const kind = candidateKind(item, metadata, fit);
|
|
734
|
+
if (constraints.kind && String(kind).toLowerCase() === String(constraints.kind).toLowerCase()) {
|
|
735
|
+
score += 8;
|
|
736
|
+
reasons.push(`kind matches ${constraints.kind}`);
|
|
737
|
+
}
|
|
738
|
+
const policy = fit.policy || item.policy || '';
|
|
739
|
+
if (policy === 'allow') {
|
|
740
|
+
score += 6;
|
|
741
|
+
reasons.push('policy allow');
|
|
742
|
+
} else if (policy === 'deny') {
|
|
743
|
+
score -= 35;
|
|
744
|
+
reasons.push('policy deny');
|
|
745
|
+
} else if (policy === 'stage_only' || policy === 'confirm') {
|
|
746
|
+
score -= 6;
|
|
747
|
+
reasons.push(`policy ${policy}`);
|
|
748
|
+
}
|
|
749
|
+
const trust = item.trust || item.agent_trust || {};
|
|
750
|
+
if (trust.review_status === 'reviewed' || trust.verified_publisher) {
|
|
751
|
+
score += 4;
|
|
752
|
+
reasons.push('reviewed or verified');
|
|
753
|
+
}
|
|
754
|
+
return { score: Math.max(0, Math.min(100, Math.round(score))), reasons };
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function buildCandidate(item, target, ranking = {}) {
|
|
758
|
+
const uuid = candidateUuid(item);
|
|
759
|
+
const metadata = itemAgentMetadata(item);
|
|
760
|
+
const fit = itemAgentFit(item);
|
|
761
|
+
const kind = candidateKind(item, metadata, fit);
|
|
762
|
+
const targets = candidateTargets(item, metadata);
|
|
763
|
+
const planTarget = target && target !== 'any' && ['codex', 'claude_code', 'gemini_cli'].includes(target)
|
|
764
|
+
? target
|
|
765
|
+
: 'codex';
|
|
766
|
+
const urlSlug = compactText(item.slug || uuid, 180);
|
|
767
|
+
const score = Number.isFinite(Number(fit.score)) ? Number(fit.score) : null;
|
|
768
|
+
const why = asArray(fit.why).map(reason => compactText(reason, 160)).filter(Boolean);
|
|
769
|
+
|
|
770
|
+
return {
|
|
771
|
+
uuid,
|
|
772
|
+
slug: compactText(item.slug || '', 180),
|
|
773
|
+
title: compactText(item.title, 160),
|
|
774
|
+
description: compactText(item.description || item.summary || '', 320),
|
|
775
|
+
url: urlSlug ? `${TOKREPO_URL}/en/workflows/${urlSlug}` : '',
|
|
776
|
+
tags: itemTags(item),
|
|
777
|
+
capability: {
|
|
778
|
+
kind,
|
|
779
|
+
install_mode: compactText(item.install_mode || item.installMode || metadata.install_mode || fit.install_mode || '', 64),
|
|
780
|
+
entrypoint: compactText(item.entrypoint || metadata.entrypoint || '', 160),
|
|
781
|
+
target_tools: targets,
|
|
782
|
+
},
|
|
783
|
+
fit: {
|
|
784
|
+
target: compactText(fit.target || target || 'any', 64),
|
|
785
|
+
score,
|
|
786
|
+
status: compactText(fit.status || item.agent_status || '', 64),
|
|
787
|
+
policy: compactText(fit.policy || item.policy || '', 64),
|
|
788
|
+
why,
|
|
789
|
+
},
|
|
790
|
+
ranking,
|
|
791
|
+
next_mcp_calls: [
|
|
792
|
+
{ tool: 'tokrepo_detail', arguments: { uuid } },
|
|
793
|
+
{ tool: 'tokrepo_install_plan', arguments: { uuid, target: planTarget } },
|
|
794
|
+
],
|
|
795
|
+
commands: {
|
|
796
|
+
inspect: `npx tokrepo detail ${uuid} --json`,
|
|
797
|
+
dry_run_install: planTarget === 'codex'
|
|
798
|
+
? `npx tokrepo install ${uuid} --dry-run --json`
|
|
799
|
+
: `npx tokrepo install ${uuid} --target ${planTarget} --dry-run --json`,
|
|
800
|
+
},
|
|
801
|
+
agent_use_contract: [
|
|
802
|
+
'Use only if the capability matches the current subtask.',
|
|
803
|
+
'Call tokrepo_detail before install to inspect content and metadata.',
|
|
804
|
+
'Call tokrepo_install_plan and respect policy_decision before writing files.',
|
|
805
|
+
'Prefer dry-run or stage-only when risk or fit is uncertain.',
|
|
806
|
+
'After using it, verify the original task outcome and record failures.',
|
|
807
|
+
],
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
|
|
580
811
|
// ─── Tool Handlers ───
|
|
581
812
|
|
|
813
|
+
async function handleDiscover(args) {
|
|
814
|
+
const task = compactText(args.task, 500);
|
|
815
|
+
if (!task) {
|
|
816
|
+
return {
|
|
817
|
+
content: [{ type: 'text', text: 'task is required for tokrepo_discover' }],
|
|
818
|
+
isError: true,
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const environment = args.environment && typeof args.environment === 'object' ? args.environment : {};
|
|
823
|
+
const constraints = args.constraints && typeof args.constraints === 'object' ? args.constraints : {};
|
|
824
|
+
const target = normalizeTarget(args.target || constraints.target || 'any');
|
|
825
|
+
const limit = clampLimit(args.limit, 6, 10);
|
|
826
|
+
const kind = compactText(args.kind || constraints.kind || '', 64);
|
|
827
|
+
const policy = compactText(args.policy || constraints.policy || '', 64);
|
|
828
|
+
const query = buildDiscoveryQuery(task, environment, constraints);
|
|
829
|
+
|
|
830
|
+
let items = [];
|
|
831
|
+
let discoveryError = '';
|
|
832
|
+
let selectedQuery = query;
|
|
833
|
+
const taskTerms = extractSearchTerms(task, 6);
|
|
834
|
+
const attempts = [
|
|
835
|
+
{ query, kind, policy },
|
|
836
|
+
{ query: taskTerms.slice(0, 3).join(' '), kind, policy },
|
|
837
|
+
{ query: taskTerms.slice(0, 2).join(' '), kind: '', policy: '' },
|
|
838
|
+
{ query: taskTerms[0] || query, kind: '', policy: '' },
|
|
839
|
+
].filter(attempt => attempt.query);
|
|
840
|
+
const seenAttempts = new Set();
|
|
841
|
+
const errors = [];
|
|
842
|
+
|
|
843
|
+
for (const attempt of attempts) {
|
|
844
|
+
const key = `${attempt.query}|${attempt.kind}|${attempt.policy}`;
|
|
845
|
+
if (seenAttempts.has(key)) continue;
|
|
846
|
+
seenAttempts.add(key);
|
|
847
|
+
const params = new URLSearchParams({
|
|
848
|
+
keyword: attempt.query,
|
|
849
|
+
page: '1',
|
|
850
|
+
page_size: String(limit),
|
|
851
|
+
sort_by: 'popular',
|
|
852
|
+
});
|
|
853
|
+
if (target && target !== 'any') params.set('target', target);
|
|
854
|
+
if (attempt.kind) params.set('kind', attempt.kind);
|
|
855
|
+
if (attempt.policy) params.set('policy', attempt.policy);
|
|
856
|
+
|
|
857
|
+
try {
|
|
858
|
+
const res = await apiGet(`/api/v1/tokenboard/workflows/list?${params}`);
|
|
859
|
+
items = res.code === 200 ? (res.data?.list || res.data?.items || []) : [];
|
|
860
|
+
if (res.code !== 200) errors.push(compactText(res.message || `API returned code ${res.code}`, 200));
|
|
861
|
+
if (items.length) {
|
|
862
|
+
selectedQuery = attempt.query;
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
865
|
+
} catch (e) {
|
|
866
|
+
errors.push(compactText(e.message, 200));
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
discoveryError = errors.find(Boolean) || '';
|
|
870
|
+
const payload = {
|
|
871
|
+
schema_version: 1,
|
|
872
|
+
intent: {
|
|
873
|
+
task,
|
|
874
|
+
target,
|
|
875
|
+
query: selectedQuery,
|
|
876
|
+
queries_tried: [...seenAttempts].map(key => key.split('|')[0]),
|
|
877
|
+
environment,
|
|
878
|
+
constraints,
|
|
879
|
+
},
|
|
880
|
+
recommended_flow: [
|
|
881
|
+
'During planning, call tokrepo_discover when the task exposes a capability gap.',
|
|
882
|
+
'Rank candidates by fit, policy, trust, and whether the entrypoint matches the active agent runtime.',
|
|
883
|
+
'Call tokrepo_detail for the top candidate before installation or use.',
|
|
884
|
+
'Call tokrepo_install_plan and respect policy_decision, rollback, and verification steps.',
|
|
885
|
+
'Dry-run or stage when the asset may write files, execute code, require secrets, or change global config.',
|
|
886
|
+
'Use the installed capability only for the matching subtask, then verify the user goal.',
|
|
887
|
+
'If the agent creates a reusable improvement, ask before publishing and use tokrepo_push with explicit files.',
|
|
888
|
+
],
|
|
889
|
+
candidates: items
|
|
890
|
+
.map(item => buildCandidate(item, target, scoreDiscoveryCandidate(item, task, target, constraints)))
|
|
891
|
+
.filter(candidate => candidate.uuid)
|
|
892
|
+
.sort((a, b) => (b.ranking?.score || 0) - (a.ranking?.score || 0))
|
|
893
|
+
.slice(0, limit),
|
|
894
|
+
empty_state: items.length ? null : {
|
|
895
|
+
message: discoveryError
|
|
896
|
+
? `TokRepo discovery could not fetch live candidates for "${selectedQuery}".`
|
|
897
|
+
: `No TokRepo candidates found for "${selectedQuery}".`,
|
|
898
|
+
error: discoveryError || undefined,
|
|
899
|
+
suggested_queries: [
|
|
900
|
+
task.split(/\s+/).slice(0, 4).join(' '),
|
|
901
|
+
compactText([kind, environment.project_type, environment.language].filter(Boolean).join(' '), 120),
|
|
902
|
+
'agent skill workflow',
|
|
903
|
+
].filter(Boolean),
|
|
904
|
+
},
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
return {
|
|
908
|
+
content: [{
|
|
909
|
+
type: 'text',
|
|
910
|
+
text: jsonText('TokRepo planning-time discovery', payload),
|
|
911
|
+
}],
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
|
|
582
915
|
async function handleSearch(args) {
|
|
583
|
-
const { query, tag, limit = 10, target = '
|
|
584
|
-
|
|
916
|
+
const { query, tag, limit = 10, target = '', kind = '', policy = '' } = args;
|
|
917
|
+
const normalizedTarget = normalizeTarget(target || 'any');
|
|
918
|
+
const targetFilter = normalizedTarget === 'any' ? '' : normalizedTarget;
|
|
919
|
+
if (targetFilter || kind || policy) {
|
|
585
920
|
const cliArgs = ['search', query, '--json', '--page-size', String(Math.min(limit, 20))];
|
|
586
|
-
if (
|
|
921
|
+
if (targetFilter) cliArgs.push('--target', targetFilter);
|
|
587
922
|
if (kind) cliArgs.push('--kind', kind);
|
|
588
923
|
if (policy) cliArgs.push('--policy', policy);
|
|
589
924
|
const { stdout, stderr } = await runTokrepoCli(cliArgs);
|
|
@@ -631,7 +966,7 @@ async function handleSearch(args) {
|
|
|
631
966
|
` ${desc}`,
|
|
632
967
|
` Tags: ${tags || 'general'} | ★ ${item.vote_count || 0} | 👁 ${item.view_count || 0}`,
|
|
633
968
|
` Plan: call \`tokrepo_install_plan\` with uuid \`${item.uuid}\``,
|
|
634
|
-
` Install: \`tokrepo install ${item.uuid} --
|
|
969
|
+
` Install: \`tokrepo install ${item.uuid} --dry-run --json\``,
|
|
635
970
|
` URL: ${TOKREPO_URL}/en/workflows/${item.uuid}`,
|
|
636
971
|
].join('\n');
|
|
637
972
|
});
|
|
@@ -663,7 +998,7 @@ async function handleDetail(args) {
|
|
|
663
998
|
`**Author**: ${w.author_name || 'Anonymous'}`,
|
|
664
999
|
`**URL**: ${TOKREPO_URL}/en/workflows/${w.uuid}`,
|
|
665
1000
|
`**Plan**: call \`tokrepo_install_plan\` with uuid \`${w.uuid}\` before installing`,
|
|
666
|
-
`**Install**: \`tokrepo install ${w.uuid} --
|
|
1001
|
+
`**Install**: \`tokrepo install ${w.uuid} --dry-run --json\``,
|
|
667
1002
|
``,
|
|
668
1003
|
steps,
|
|
669
1004
|
].join('\n');
|
|
@@ -689,8 +1024,8 @@ async function handleInstallPlan(args) {
|
|
|
689
1024
|
const plan = await fetchInstallPlan(uuid, target);
|
|
690
1025
|
const decision = planPolicyDecision(plan);
|
|
691
1026
|
const command = decision === 'allow'
|
|
692
|
-
? `tokrepo install ${plan.asset_uuid || uuid} --target
|
|
693
|
-
: `tokrepo install ${plan.asset_uuid || uuid} --target
|
|
1027
|
+
? `tokrepo install ${plan.asset_uuid || uuid} --target ${target} --yes`
|
|
1028
|
+
: `tokrepo install ${plan.asset_uuid || uuid} --target ${target} --dry-run --json`;
|
|
694
1029
|
return {
|
|
695
1030
|
content: [{
|
|
696
1031
|
type: 'text',
|
|
@@ -1056,7 +1391,18 @@ async function handleRequest(msg) {
|
|
|
1056
1391
|
const { name, arguments: args } = params || {};
|
|
1057
1392
|
let result;
|
|
1058
1393
|
try {
|
|
1394
|
+
if (!EXPOSED_TOOL_NAMES.has(name)) {
|
|
1395
|
+
return {
|
|
1396
|
+
jsonrpc: '2.0',
|
|
1397
|
+
id,
|
|
1398
|
+
result: {
|
|
1399
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
1400
|
+
isError: true,
|
|
1401
|
+
},
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1059
1404
|
switch (name) {
|
|
1405
|
+
case 'tokrepo_discover': result = await handleDiscover(args || {}); break;
|
|
1060
1406
|
case 'tokrepo_search': result = await handleSearch(args || {}); break;
|
|
1061
1407
|
case 'tokrepo_detail': result = await handleDetail(args || {}); break;
|
|
1062
1408
|
case 'tokrepo_install': result = await handleInstall(args || {}); break;
|
package/package.json
CHANGED