tokrepo-mcp-server 2.4.0 → 2.6.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 +9 -11
- package/bin/server.js +380 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,44 +35,42 @@ 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
|
-
- **Lifecycle control** — list
|
|
44
|
-
- **Agent eval** — run TokRepo's agent-native search/plan/install/rollback contract checks
|
|
44
|
+
- **Lifecycle control** — list, update, uninstall, and roll back managed Codex installs
|
|
45
45
|
|
|
46
46
|
## Available Tools
|
|
47
47
|
|
|
48
48
|
| Tool | Description |
|
|
49
49
|
|------|-------------|
|
|
50
|
+
| `tokrepo_discover` | Planning-time capability discovery from a task, environment, and constraints |
|
|
50
51
|
| `tokrepo_search` | Search assets by keyword/tag with `agent_fit` ranking |
|
|
51
52
|
| `tokrepo_detail` | Get full asset details by UUID |
|
|
52
53
|
| `tokrepo_install_plan` | Get agent-native install plan v2 |
|
|
53
54
|
| `tokrepo_codex_install` | Dry-run, stage, or install a Codex skill safely |
|
|
54
|
-
| `tokrepo_clone_plan` | Bulk profile clone dry-run plan |
|
|
55
55
|
| `tokrepo_installed` | List TokRepo-managed Codex installs |
|
|
56
|
+
| `tokrepo_update` | Dry-run or update managed Codex installs |
|
|
56
57
|
| `tokrepo_uninstall` | Dry-run or remove a managed Codex install |
|
|
57
58
|
| `tokrepo_rollback` | Dry-run or roll back a prior Codex install session |
|
|
58
|
-
| `
|
|
59
|
-
| `tokrepo_install` | Get raw installable content |
|
|
60
|
-
| `tokrepo_trending` | Browse popular/latest assets |
|
|
59
|
+
| `tokrepo_push` | Push one explicit asset to TokRepo after user confirmation |
|
|
61
60
|
|
|
62
61
|
## Example Conversations
|
|
63
62
|
|
|
64
63
|
```
|
|
65
64
|
You: "Find me a good MCP server for databases"
|
|
66
|
-
AI: [calls
|
|
65
|
+
AI: [calls tokrepo_discover] → Ranks DBHub, Supabase MCP, PostgreSQL MCP as candidate capabilities
|
|
67
66
|
|
|
68
|
-
You: "What
|
|
69
|
-
AI: [calls
|
|
67
|
+
You: "What video assets should I install?"
|
|
68
|
+
AI: [calls tokrepo_discover] → Finds relevant skills, checks fit and policy, then asks before installing
|
|
70
69
|
|
|
71
70
|
You: "Install that cursor rules asset"
|
|
72
71
|
AI: [calls tokrepo_install_plan] → Reviews policy and actions
|
|
73
72
|
AI: [calls tokrepo_codex_install with dry_run=false, confirm=true] → Writes only after explicit confirmation
|
|
74
73
|
AI: [calls tokrepo_rollback with dry_run=true] → Shows exactly what would be removed before rollback
|
|
75
|
-
AI: [calls tokrepo_eval_agent] → Verifies TokRepo's agent-facing flow end to end
|
|
76
74
|
```
|
|
77
75
|
|
|
78
76
|
## Why TokRepo?
|
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.6.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
|
},
|
|
@@ -184,6 +218,35 @@ const TOOLS = [
|
|
|
184
218
|
properties: {},
|
|
185
219
|
},
|
|
186
220
|
},
|
|
221
|
+
{
|
|
222
|
+
name: 'tokrepo_update',
|
|
223
|
+
description: 'Check or update TokRepo-managed Codex assets from the local manifest. Defaults to dry_run=true. To write updates, set dry_run=false and confirm=true.',
|
|
224
|
+
inputSchema: {
|
|
225
|
+
type: 'object',
|
|
226
|
+
properties: {
|
|
227
|
+
dry_run: {
|
|
228
|
+
type: 'boolean',
|
|
229
|
+
description: 'When true, check for updates and return the plan without writing files. Default true.',
|
|
230
|
+
default: true,
|
|
231
|
+
},
|
|
232
|
+
confirm: {
|
|
233
|
+
type: 'boolean',
|
|
234
|
+
description: 'Required when dry_run=false to prevent accidental writes.',
|
|
235
|
+
default: false,
|
|
236
|
+
},
|
|
237
|
+
stage: {
|
|
238
|
+
type: 'boolean',
|
|
239
|
+
description: 'Stage risky updates under ~/.codex/tokrepo/staged instead of activating them.',
|
|
240
|
+
default: false,
|
|
241
|
+
},
|
|
242
|
+
approve_risk: {
|
|
243
|
+
type: 'boolean',
|
|
244
|
+
description: 'Allow updates whose install policy requires explicit risk approval.',
|
|
245
|
+
default: false,
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
187
250
|
{
|
|
188
251
|
name: 'tokrepo_uninstall',
|
|
189
252
|
description: 'Safely uninstall a TokRepo-managed Codex asset. Defaults to dry_run=true. To remove files, set dry_run=false and confirm=true. Local changes are blocked unless force=true.',
|
|
@@ -367,6 +430,21 @@ const TOOLS = [
|
|
|
367
430
|
},
|
|
368
431
|
];
|
|
369
432
|
|
|
433
|
+
const EXPOSED_TOOL_NAMES = new Set([
|
|
434
|
+
'tokrepo_discover',
|
|
435
|
+
'tokrepo_search',
|
|
436
|
+
'tokrepo_detail',
|
|
437
|
+
'tokrepo_install_plan',
|
|
438
|
+
'tokrepo_codex_install',
|
|
439
|
+
'tokrepo_installed',
|
|
440
|
+
'tokrepo_update',
|
|
441
|
+
'tokrepo_uninstall',
|
|
442
|
+
'tokrepo_rollback',
|
|
443
|
+
'tokrepo_push',
|
|
444
|
+
]);
|
|
445
|
+
|
|
446
|
+
const EXPOSED_TOOLS = TOOLS.filter((tool) => EXPOSED_TOOL_NAMES.has(tool.name));
|
|
447
|
+
|
|
370
448
|
// ─── HTTP Helper ───
|
|
371
449
|
|
|
372
450
|
function apiGet(path) {
|
|
@@ -534,13 +612,255 @@ function jsonText(title, data) {
|
|
|
534
612
|
return `${title}\n\n\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\``;
|
|
535
613
|
}
|
|
536
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 buildCandidate(item, target) {
|
|
705
|
+
const uuid = candidateUuid(item);
|
|
706
|
+
const metadata = itemAgentMetadata(item);
|
|
707
|
+
const fit = itemAgentFit(item);
|
|
708
|
+
const kind = candidateKind(item, metadata, fit);
|
|
709
|
+
const targets = candidateTargets(item, metadata);
|
|
710
|
+
const planTarget = target && target !== 'any' && ['codex', 'claude_code', 'gemini_cli'].includes(target)
|
|
711
|
+
? target
|
|
712
|
+
: 'codex';
|
|
713
|
+
const urlSlug = compactText(item.slug || uuid, 180);
|
|
714
|
+
const score = Number.isFinite(Number(fit.score)) ? Number(fit.score) : null;
|
|
715
|
+
const why = asArray(fit.why).map(reason => compactText(reason, 160)).filter(Boolean);
|
|
716
|
+
|
|
717
|
+
return {
|
|
718
|
+
uuid,
|
|
719
|
+
slug: compactText(item.slug || '', 180),
|
|
720
|
+
title: compactText(item.title, 160),
|
|
721
|
+
description: compactText(item.description || item.summary || '', 320),
|
|
722
|
+
url: urlSlug ? `${TOKREPO_URL}/en/workflows/${urlSlug}` : '',
|
|
723
|
+
tags: itemTags(item),
|
|
724
|
+
capability: {
|
|
725
|
+
kind,
|
|
726
|
+
install_mode: compactText(item.install_mode || item.installMode || metadata.install_mode || fit.install_mode || '', 64),
|
|
727
|
+
entrypoint: compactText(item.entrypoint || metadata.entrypoint || '', 160),
|
|
728
|
+
target_tools: targets,
|
|
729
|
+
},
|
|
730
|
+
fit: {
|
|
731
|
+
target: compactText(fit.target || target || 'any', 64),
|
|
732
|
+
score,
|
|
733
|
+
status: compactText(fit.status || item.agent_status || '', 64),
|
|
734
|
+
policy: compactText(fit.policy || item.policy || '', 64),
|
|
735
|
+
why,
|
|
736
|
+
},
|
|
737
|
+
next_mcp_calls: [
|
|
738
|
+
{ tool: 'tokrepo_detail', arguments: { uuid } },
|
|
739
|
+
{ tool: 'tokrepo_install_plan', arguments: { uuid, target: planTarget } },
|
|
740
|
+
],
|
|
741
|
+
commands: {
|
|
742
|
+
inspect: `npx tokrepo detail ${uuid} --json`,
|
|
743
|
+
dry_run_install: planTarget === 'codex'
|
|
744
|
+
? `npx tokrepo install ${uuid} --dry-run --json`
|
|
745
|
+
: `npx tokrepo install ${uuid} --target ${planTarget} --dry-run --json`,
|
|
746
|
+
},
|
|
747
|
+
agent_use_contract: [
|
|
748
|
+
'Use only if the capability matches the current subtask.',
|
|
749
|
+
'Call tokrepo_detail before install to inspect content and metadata.',
|
|
750
|
+
'Call tokrepo_install_plan and respect policy_decision before writing files.',
|
|
751
|
+
'Prefer dry-run or stage-only when risk or fit is uncertain.',
|
|
752
|
+
'After using it, verify the original task outcome and record failures.',
|
|
753
|
+
],
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
|
|
537
757
|
// ─── Tool Handlers ───
|
|
538
758
|
|
|
759
|
+
async function handleDiscover(args) {
|
|
760
|
+
const task = compactText(args.task, 500);
|
|
761
|
+
if (!task) {
|
|
762
|
+
return {
|
|
763
|
+
content: [{ type: 'text', text: 'task is required for tokrepo_discover' }],
|
|
764
|
+
isError: true,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const environment = args.environment && typeof args.environment === 'object' ? args.environment : {};
|
|
769
|
+
const constraints = args.constraints && typeof args.constraints === 'object' ? args.constraints : {};
|
|
770
|
+
const target = normalizeTarget(args.target || constraints.target || 'any');
|
|
771
|
+
const limit = clampLimit(args.limit, 6, 10);
|
|
772
|
+
const kind = compactText(args.kind || constraints.kind || '', 64);
|
|
773
|
+
const policy = compactText(args.policy || constraints.policy || '', 64);
|
|
774
|
+
const query = buildDiscoveryQuery(task, environment, constraints);
|
|
775
|
+
|
|
776
|
+
let items = [];
|
|
777
|
+
let discoveryError = '';
|
|
778
|
+
let selectedQuery = query;
|
|
779
|
+
const taskTerms = extractSearchTerms(task, 6);
|
|
780
|
+
const attempts = [
|
|
781
|
+
{ query, kind, policy },
|
|
782
|
+
{ query: taskTerms.slice(0, 3).join(' '), kind, policy },
|
|
783
|
+
{ query: taskTerms.slice(0, 2).join(' '), kind: '', policy: '' },
|
|
784
|
+
{ query: taskTerms[0] || query, kind: '', policy: '' },
|
|
785
|
+
].filter(attempt => attempt.query);
|
|
786
|
+
const seenAttempts = new Set();
|
|
787
|
+
const errors = [];
|
|
788
|
+
|
|
789
|
+
for (const attempt of attempts) {
|
|
790
|
+
const key = `${attempt.query}|${attempt.kind}|${attempt.policy}`;
|
|
791
|
+
if (seenAttempts.has(key)) continue;
|
|
792
|
+
seenAttempts.add(key);
|
|
793
|
+
const params = new URLSearchParams({
|
|
794
|
+
keyword: attempt.query,
|
|
795
|
+
page: '1',
|
|
796
|
+
page_size: String(limit),
|
|
797
|
+
sort_by: 'popular',
|
|
798
|
+
});
|
|
799
|
+
if (target && target !== 'any') params.set('target', target);
|
|
800
|
+
if (attempt.kind) params.set('kind', attempt.kind);
|
|
801
|
+
if (attempt.policy) params.set('policy', attempt.policy);
|
|
802
|
+
|
|
803
|
+
try {
|
|
804
|
+
const res = await apiGet(`/api/v1/tokenboard/workflows/list?${params}`);
|
|
805
|
+
items = res.code === 200 ? (res.data?.list || res.data?.items || []) : [];
|
|
806
|
+
if (res.code !== 200) errors.push(compactText(res.message || `API returned code ${res.code}`, 200));
|
|
807
|
+
if (items.length) {
|
|
808
|
+
selectedQuery = attempt.query;
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
811
|
+
} catch (e) {
|
|
812
|
+
errors.push(compactText(e.message, 200));
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
discoveryError = errors.find(Boolean) || '';
|
|
816
|
+
const payload = {
|
|
817
|
+
schema_version: 1,
|
|
818
|
+
intent: {
|
|
819
|
+
task,
|
|
820
|
+
target,
|
|
821
|
+
query: selectedQuery,
|
|
822
|
+
queries_tried: [...seenAttempts].map(key => key.split('|')[0]),
|
|
823
|
+
environment,
|
|
824
|
+
constraints,
|
|
825
|
+
},
|
|
826
|
+
recommended_flow: [
|
|
827
|
+
'During planning, call tokrepo_discover when the task exposes a capability gap.',
|
|
828
|
+
'Rank candidates by fit, policy, trust, and whether the entrypoint matches the active agent runtime.',
|
|
829
|
+
'Call tokrepo_detail for the top candidate before installation or use.',
|
|
830
|
+
'Call tokrepo_install_plan and respect policy_decision, rollback, and verification steps.',
|
|
831
|
+
'Dry-run or stage when the asset may write files, execute code, require secrets, or change global config.',
|
|
832
|
+
'Use the installed capability only for the matching subtask, then verify the user goal.',
|
|
833
|
+
'If the agent creates a reusable improvement, ask before publishing and use tokrepo_push with explicit files.',
|
|
834
|
+
],
|
|
835
|
+
candidates: items.slice(0, limit).map(item => buildCandidate(item, target)).filter(candidate => candidate.uuid),
|
|
836
|
+
empty_state: items.length ? null : {
|
|
837
|
+
message: discoveryError
|
|
838
|
+
? `TokRepo discovery could not fetch live candidates for "${selectedQuery}".`
|
|
839
|
+
: `No TokRepo candidates found for "${selectedQuery}".`,
|
|
840
|
+
error: discoveryError || undefined,
|
|
841
|
+
suggested_queries: [
|
|
842
|
+
task.split(/\s+/).slice(0, 4).join(' '),
|
|
843
|
+
compactText([kind, environment.project_type, environment.language].filter(Boolean).join(' '), 120),
|
|
844
|
+
'agent skill workflow',
|
|
845
|
+
].filter(Boolean),
|
|
846
|
+
},
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
return {
|
|
850
|
+
content: [{
|
|
851
|
+
type: 'text',
|
|
852
|
+
text: jsonText('TokRepo planning-time discovery', payload),
|
|
853
|
+
}],
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
|
|
539
857
|
async function handleSearch(args) {
|
|
540
|
-
const { query, tag, limit = 10, target = '
|
|
541
|
-
|
|
858
|
+
const { query, tag, limit = 10, target = '', kind = '', policy = '' } = args;
|
|
859
|
+
const normalizedTarget = normalizeTarget(target || 'any');
|
|
860
|
+
const targetFilter = normalizedTarget === 'any' ? '' : normalizedTarget;
|
|
861
|
+
if (targetFilter || kind || policy) {
|
|
542
862
|
const cliArgs = ['search', query, '--json', '--page-size', String(Math.min(limit, 20))];
|
|
543
|
-
if (
|
|
863
|
+
if (targetFilter) cliArgs.push('--target', targetFilter);
|
|
544
864
|
if (kind) cliArgs.push('--kind', kind);
|
|
545
865
|
if (policy) cliArgs.push('--policy', policy);
|
|
546
866
|
const { stdout, stderr } = await runTokrepoCli(cliArgs);
|
|
@@ -588,7 +908,7 @@ async function handleSearch(args) {
|
|
|
588
908
|
` ${desc}`,
|
|
589
909
|
` Tags: ${tags || 'general'} | ★ ${item.vote_count || 0} | 👁 ${item.view_count || 0}`,
|
|
590
910
|
` Plan: call \`tokrepo_install_plan\` with uuid \`${item.uuid}\``,
|
|
591
|
-
` Install: \`tokrepo install ${item.uuid} --
|
|
911
|
+
` Install: \`tokrepo install ${item.uuid} --dry-run --json\``,
|
|
592
912
|
` URL: ${TOKREPO_URL}/en/workflows/${item.uuid}`,
|
|
593
913
|
].join('\n');
|
|
594
914
|
});
|
|
@@ -620,7 +940,7 @@ async function handleDetail(args) {
|
|
|
620
940
|
`**Author**: ${w.author_name || 'Anonymous'}`,
|
|
621
941
|
`**URL**: ${TOKREPO_URL}/en/workflows/${w.uuid}`,
|
|
622
942
|
`**Plan**: call \`tokrepo_install_plan\` with uuid \`${w.uuid}\` before installing`,
|
|
623
|
-
`**Install**: \`tokrepo install ${w.uuid} --
|
|
943
|
+
`**Install**: \`tokrepo install ${w.uuid} --dry-run --json\``,
|
|
624
944
|
``,
|
|
625
945
|
steps,
|
|
626
946
|
].join('\n');
|
|
@@ -646,8 +966,8 @@ async function handleInstallPlan(args) {
|
|
|
646
966
|
const plan = await fetchInstallPlan(uuid, target);
|
|
647
967
|
const decision = planPolicyDecision(plan);
|
|
648
968
|
const command = decision === 'allow'
|
|
649
|
-
? `tokrepo install ${plan.asset_uuid || uuid} --target
|
|
650
|
-
: `tokrepo install ${plan.asset_uuid || uuid} --target
|
|
969
|
+
? `tokrepo install ${plan.asset_uuid || uuid} --target ${target} --yes`
|
|
970
|
+
: `tokrepo install ${plan.asset_uuid || uuid} --target ${target} --dry-run --json`;
|
|
651
971
|
return {
|
|
652
972
|
content: [{
|
|
653
973
|
type: 'text',
|
|
@@ -757,6 +1077,39 @@ async function handleInstalled() {
|
|
|
757
1077
|
return { content: [{ type: 'text', text: jsonText('TokRepo Codex installed assets', data) }] };
|
|
758
1078
|
}
|
|
759
1079
|
|
|
1080
|
+
async function handleUpdate(args) {
|
|
1081
|
+
const { dry_run = true, confirm = false, stage = false, approve_risk = false } = args || {};
|
|
1082
|
+
const cliArgs = ['sync-installed', '--target', 'codex', '--json'];
|
|
1083
|
+
if (dry_run !== false) cliArgs.push('--dry-run');
|
|
1084
|
+
if (stage) cliArgs.push('--stage');
|
|
1085
|
+
if (approve_risk) cliArgs.push('--approve-mcp');
|
|
1086
|
+
|
|
1087
|
+
if (dry_run === false && !confirm) {
|
|
1088
|
+
cliArgs.push('--dry-run');
|
|
1089
|
+
const { stdout, stderr } = await runTokrepoCli(cliArgs);
|
|
1090
|
+
let data;
|
|
1091
|
+
try {
|
|
1092
|
+
data = JSON.parse(stdout);
|
|
1093
|
+
} catch {
|
|
1094
|
+
data = { stdout, stderr };
|
|
1095
|
+
}
|
|
1096
|
+
return {
|
|
1097
|
+
isError: true,
|
|
1098
|
+
content: [{ type: 'text', text: jsonText('Refused to update because confirm=true was not provided. Dry-run plan follows.', data) }],
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
if (dry_run === false) cliArgs.push('--update');
|
|
1103
|
+
const { stdout, stderr } = await runTokrepoCli(cliArgs);
|
|
1104
|
+
let data;
|
|
1105
|
+
try {
|
|
1106
|
+
data = JSON.parse(stdout);
|
|
1107
|
+
} catch {
|
|
1108
|
+
data = { stdout, stderr };
|
|
1109
|
+
}
|
|
1110
|
+
return { content: [{ type: 'text', text: jsonText(dry_run === false ? 'TokRepo Codex update result' : 'TokRepo Codex update dry-run', data) }] };
|
|
1111
|
+
}
|
|
1112
|
+
|
|
760
1113
|
async function handleUninstall(args) {
|
|
761
1114
|
const { uuid, dry_run = true, confirm = false, force = false } = args;
|
|
762
1115
|
const cliArgs = ['uninstall', uuid, '--target', 'codex', '--json'];
|
|
@@ -974,13 +1327,24 @@ async function handleRequest(msg) {
|
|
|
974
1327
|
return null; // no response for notifications
|
|
975
1328
|
|
|
976
1329
|
case 'tools/list':
|
|
977
|
-
return { jsonrpc: '2.0', id, result: { tools:
|
|
1330
|
+
return { jsonrpc: '2.0', id, result: { tools: EXPOSED_TOOLS } };
|
|
978
1331
|
|
|
979
1332
|
case 'tools/call': {
|
|
980
1333
|
const { name, arguments: args } = params || {};
|
|
981
1334
|
let result;
|
|
982
1335
|
try {
|
|
1336
|
+
if (!EXPOSED_TOOL_NAMES.has(name)) {
|
|
1337
|
+
return {
|
|
1338
|
+
jsonrpc: '2.0',
|
|
1339
|
+
id,
|
|
1340
|
+
result: {
|
|
1341
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
1342
|
+
isError: true,
|
|
1343
|
+
},
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
983
1346
|
switch (name) {
|
|
1347
|
+
case 'tokrepo_discover': result = await handleDiscover(args || {}); break;
|
|
984
1348
|
case 'tokrepo_search': result = await handleSearch(args || {}); break;
|
|
985
1349
|
case 'tokrepo_detail': result = await handleDetail(args || {}); break;
|
|
986
1350
|
case 'tokrepo_install': result = await handleInstall(args || {}); break;
|
|
@@ -988,6 +1352,7 @@ async function handleRequest(msg) {
|
|
|
988
1352
|
case 'tokrepo_codex_install': result = await handleCodexInstall(args || {}); break;
|
|
989
1353
|
case 'tokrepo_clone_plan': result = await handleClonePlan(args || {}); break;
|
|
990
1354
|
case 'tokrepo_installed': result = await handleInstalled(args || {}); break;
|
|
1355
|
+
case 'tokrepo_update': result = await handleUpdate(args || {}); break;
|
|
991
1356
|
case 'tokrepo_uninstall': result = await handleUninstall(args || {}); break;
|
|
992
1357
|
case 'tokrepo_rollback': result = await handleRollback(args || {}); break;
|
|
993
1358
|
case 'tokrepo_eval_agent': result = await handleEvalAgent(args || {}); break;
|
package/package.json
CHANGED