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.
Files changed (3) hide show
  1. package/README.md +9 -11
  2. package/bin/server.js +380 -15
  3. 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
- - **Search** 200+ curated AI assets by keyword or category with Codex-first `agent_fit` ranking
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 installed assets, uninstall managed files, and roll back install sessions
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
- | `tokrepo_eval_agent` | Run agent-native CLI contract and lifecycle evals |
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 tokrepo_search] → Shows DBHub, Supabase MCP, PostgreSQL MCP with install commands
65
+ AI: [calls tokrepo_discover] → Ranks DBHub, Supabase MCP, PostgreSQL MCP as candidate capabilities
67
66
 
68
- You: "What's trending on TokRepo?"
69
- AI: [calls tokrepo_trending] → Shows top assets by popularity
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.4.0';
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, Claude skills, prompts, or workflows.',
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: 'Agent target filter. Defaults to codex so results include agent_fit ranking for installable Codex assets.',
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. Currently codex is supported.',
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 = 'codex', kind = '', policy = '' } = args;
541
- if (target || kind || policy) {
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 (target) cliArgs.push('--target', target);
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} --target codex --dry-run --json\``,
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} --target codex --dry-run --json\``,
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 codex --yes`
650
- : `tokrepo install ${plan.asset_uuid || uuid} --target codex --dry-run --json`;
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: 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokrepo-mcp-server",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "Agent-native MCP server for TokRepo — search, plan, safely install, and push AI assets from MCP clients.",
5
5
  "mcpName": "io.github.tokrepo/mcp-server",
6
6
  "bin": {