@ttoss/http-server-mcp 0.34.0 → 0.34.2

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 CHANGED
@@ -484,14 +484,14 @@ registerTools({ server: fullServer, tools });
484
484
  registerTools({ server: deferredServer, tools, defer: true });
485
485
  ```
486
486
 
487
- A schema enters the model's context only for the tools it is about to use. `call` enforces the tool's schema as a direct call would, and its errors help the model recover: an unknown name answers suggestions, and invalid arguments, a tool's own error result, or a thrown error carry the input schema, so a `call` made without `describe` corrects itself in one retry.
487
+ A schema enters the model's context only for the tools it is about to use. `call` enforces the tool's schema as a direct call would, and its errors help the model recover: an unknown name answers suggestions (close typos first, then near-misses and tools sharing the name's words, by how much they share), and invalid arguments, a tool's own error result, or a thrown error carry the input schema, so a `call` made without `describe` corrects itself in one retry.
488
488
 
489
489
  Pass options instead of `true` to tune it:
490
490
 
491
491
  - `except` — tools kept standalone, not deferred; they stay reachable through `search` too. Defaults to those linked to an [MCP Apps](#mcp-apps-interactive-uis) view, since a host finds a view through the tool's own `tools/list` entry.
492
492
  - `visible` — runs on every `search`, `describe` and `call`; a tool it rejects is absent from all three. Standalone tools stay listed.
493
493
  - `search` — replaces the default ranking, `rankTools`, e.g. with embeddings.
494
- - `searchLimit` (default `10`) and `names` (default `search` / `describe` / `call`).
494
+ - `searchLimit` (default `10`), `searchMaxLimit` — the largest `limit` accepted (default `50`, or `searchLimit` when higher) — and `names` (default `search` / `describe` / `call`).
495
495
 
496
496
  A gate (below) sees each call with the tool's own name and arguments, so authorization stays per tool when deferred.
497
497
 
package/dist/index.cjs CHANGED
@@ -573,23 +573,29 @@ var scoreOf = ({
573
573
  }
574
574
  return score;
575
575
  };
576
+ var withTag = ({
577
+ tag,
578
+ tools
579
+ }) => {
580
+ return tag === void 0 ? tools : tools.filter(tool => {
581
+ return tool.tags?.includes(tag) ?? false;
582
+ });
583
+ };
576
584
  /**
577
- * The deferred tools' default search: scores each tool by the query's terms,
578
- * weighted name > summary > tags > description, and breaks ties by the
579
- * tools' order. No index — a few hundred tools need none.
585
+ * The tools `query` matches, best first, ties in the tools' order; `undefined`
586
+ * when the query has no words.
580
587
  */
581
- var rankTools = ({
588
+ var scoreTools = ({
582
589
  query,
583
590
  tag,
584
- limit,
585
591
  tools
586
592
  }) => {
587
- const candidates = tag === void 0 ? tools : tools.filter(tool => {
588
- return tool.tags?.includes(tag) ?? false;
589
- });
590
593
  const terms = [...new Set(termsOf(query))];
591
- if (terms.length === 0) return candidates.slice(0, limit);
592
- return candidates.map((tool, index) => {
594
+ if (terms.length === 0) return void 0;
595
+ return withTag({
596
+ tag,
597
+ tools
598
+ }).map((tool, index) => {
593
599
  return {
594
600
  tool,
595
601
  index,
@@ -604,7 +610,29 @@ var rankTools = ({
604
610
  return score > 0;
605
611
  }).sort((a, b) => {
606
612
  return b.score - a.score || a.index - b.index;
607
- }).slice(0, limit).map(({
613
+ });
614
+ };
615
+ /**
616
+ * The deferred tools' default search: scores each tool by the query's terms,
617
+ * weighted name > summary > tags > description, and breaks ties by the
618
+ * tools' order. No index — a few hundred tools need none.
619
+ */
620
+ var rankTools = ({
621
+ query,
622
+ tag,
623
+ limit,
624
+ tools
625
+ }) => {
626
+ const scored = scoreTools({
627
+ query,
628
+ tag,
629
+ tools
630
+ });
631
+ if (!scored) return withTag({
632
+ tag,
633
+ tools
634
+ }).slice(0, limit);
635
+ return scored.slice(0, limit).map(({
608
636
  tool
609
637
  }) => {
610
638
  return tool;
@@ -631,39 +659,53 @@ var editDistance = (a, b) => {
631
659
  }
632
660
  return row[b.length];
633
661
  };
634
- /** Names a model probably meant: near-misses first, then related tools. */
662
+ /**
663
+ * Names a model probably meant: close typos first, nearest first; then the
664
+ * near-misses and the tools sharing the name's words, by word score, so a tool
665
+ * holding every word outranks a distant near-miss.
666
+ */
635
667
  var suggestionsFor = ({
636
668
  name,
637
669
  tools
638
670
  }) => {
639
671
  const tolerance = Math.max(2, Math.floor(name.length / 3));
640
- const nearMisses = tools.map(tool => {
672
+ const scores = new Map((scoreTools({
673
+ query: name,
674
+ tools
675
+ }) ?? []).map(({
676
+ tool,
677
+ score
678
+ }) => {
679
+ return [tool, score];
680
+ }));
681
+ const rankOf = distance => {
682
+ return Math.min(distance, 3);
683
+ };
684
+ return tools.map((tool, index) => {
641
685
  return {
642
- name: tool.name,
643
- distance: editDistance(name, tool.name)
686
+ tool,
687
+ index,
688
+ distance: editDistance(name, tool.name),
689
+ score: scores.get(tool) ?? 0
644
690
  };
645
691
  }).filter(({
646
- distance
692
+ distance,
693
+ score
647
694
  }) => {
648
- return distance <= tolerance;
695
+ return distance <= tolerance || score > 0;
649
696
  }).sort((a, b) => {
650
- return a.distance - b.distance;
651
- }).map(candidate => {
652
- return candidate.name;
653
- });
654
- const related = rankTools({
655
- query: name,
656
- limit: SUGGESTION_COUNT,
657
- tools
658
- }).map(tool => {
697
+ return rankOf(a.distance) - rankOf(b.distance) || b.score - a.score || a.distance - b.distance || a.index - b.index;
698
+ }).slice(0, SUGGESTION_COUNT).map(({
699
+ tool
700
+ }) => {
659
701
  return tool.name;
660
702
  });
661
- return [...new Set([...nearMisses, ...related])].slice(0, SUGGESTION_COUNT);
662
703
  };
663
704
 
664
705
  //#endregion
665
706
  //#region src/deferTools.ts
666
707
  var DEFAULT_SEARCH_LIMIT = 10;
708
+ var DEFAULT_SEARCH_MAX_LIMIT = 50;
667
709
  var jsonResult = ({
668
710
  data,
669
711
  isError = false
@@ -708,6 +750,7 @@ var deferredToolsOf = ({
708
750
  tool
709
751
  }) : true;
710
752
  };
753
+ const searchLimit = options.searchLimit ?? DEFAULT_SEARCH_LIMIT;
711
754
  return {
712
755
  names: {
713
756
  search: "search",
@@ -716,7 +759,8 @@ var deferredToolsOf = ({
716
759
  ...options.names
717
760
  },
718
761
  tools,
719
- searchLimit: options.searchLimit ?? DEFAULT_SEARCH_LIMIT,
762
+ searchLimit,
763
+ searchMaxLimit: options.searchMaxLimit ?? Math.max(DEFAULT_SEARCH_MAX_LIMIT, searchLimit),
720
764
  searchTools: options.search ?? rankTools,
721
765
  visibleTools: () => {
722
766
  return tools.filter(isVisible);
@@ -731,6 +775,7 @@ var searchToolOf = ({
731
775
  names,
732
776
  tools,
733
777
  searchLimit,
778
+ searchMaxLimit,
734
779
  searchTools,
735
780
  visibleTools
736
781
  }) => {
@@ -745,7 +790,7 @@ var searchToolOf = ({
745
790
  properties: {
746
791
  query: {
747
792
  type: "string",
748
- description: "Words describing the action, e.g. \"create agent\". Empty lists every tool."
793
+ description: "Words describing the action, e.g. \"create agent\". Empty lists the tools in order."
749
794
  },
750
795
  tag: {
751
796
  type: "string",
@@ -757,6 +802,7 @@ var searchToolOf = ({
757
802
  limit: {
758
803
  type: "integer",
759
804
  minimum: 1,
805
+ maximum: searchMaxLimit,
760
806
  description: `Maximum number of results. Defaults to ${searchLimit}.`
761
807
  }
762
808
  },
package/dist/index.d.cts CHANGED
@@ -865,6 +865,12 @@ interface DeferToolsOptions {
865
865
  * @default 10
866
866
  */
867
867
  searchLimit?: number;
868
+ /**
869
+ * The largest `limit` `search` accepts. With no ceiling, an empty query
870
+ * lists every tool, more than a client's context holds.
871
+ * @default 50, or `searchLimit` when that is higher
872
+ */
873
+ searchMaxLimit?: number;
868
874
  /**
869
875
  * Runs on every `search`, `describe` and `call`. A tool it rejects is
870
876
  * absent from all three; `call` answers it as an unknown name. Tools
package/dist/index.d.mts CHANGED
@@ -866,6 +866,12 @@ interface DeferToolsOptions {
866
866
  * @default 10
867
867
  */
868
868
  searchLimit?: number;
869
+ /**
870
+ * The largest `limit` `search` accepts. With no ceiling, an empty query
871
+ * lists every tool, more than a client's context holds.
872
+ * @default 50, or `searchLimit` when that is higher
873
+ */
874
+ searchMaxLimit?: number;
869
875
  /**
870
876
  * Runs on every `search`, `describe` and `call`. A tool it rejects is
871
877
  * absent from all three; `call` answers it as an unknown name. Tools
package/dist/index.mjs CHANGED
@@ -570,23 +570,29 @@ var scoreOf = ({
570
570
  }
571
571
  return score;
572
572
  };
573
+ var withTag = ({
574
+ tag,
575
+ tools
576
+ }) => {
577
+ return tag === void 0 ? tools : tools.filter(tool => {
578
+ return tool.tags?.includes(tag) ?? false;
579
+ });
580
+ };
573
581
  /**
574
- * The deferred tools' default search: scores each tool by the query's terms,
575
- * weighted name > summary > tags > description, and breaks ties by the
576
- * tools' order. No index — a few hundred tools need none.
582
+ * The tools `query` matches, best first, ties in the tools' order; `undefined`
583
+ * when the query has no words.
577
584
  */
578
- var rankTools = ({
585
+ var scoreTools = ({
579
586
  query,
580
587
  tag,
581
- limit,
582
588
  tools
583
589
  }) => {
584
- const candidates = tag === void 0 ? tools : tools.filter(tool => {
585
- return tool.tags?.includes(tag) ?? false;
586
- });
587
590
  const terms = [...new Set(termsOf(query))];
588
- if (terms.length === 0) return candidates.slice(0, limit);
589
- return candidates.map((tool, index) => {
591
+ if (terms.length === 0) return void 0;
592
+ return withTag({
593
+ tag,
594
+ tools
595
+ }).map((tool, index) => {
590
596
  return {
591
597
  tool,
592
598
  index,
@@ -601,7 +607,29 @@ var rankTools = ({
601
607
  return score > 0;
602
608
  }).sort((a, b) => {
603
609
  return b.score - a.score || a.index - b.index;
604
- }).slice(0, limit).map(({
610
+ });
611
+ };
612
+ /**
613
+ * The deferred tools' default search: scores each tool by the query's terms,
614
+ * weighted name > summary > tags > description, and breaks ties by the
615
+ * tools' order. No index — a few hundred tools need none.
616
+ */
617
+ var rankTools = ({
618
+ query,
619
+ tag,
620
+ limit,
621
+ tools
622
+ }) => {
623
+ const scored = scoreTools({
624
+ query,
625
+ tag,
626
+ tools
627
+ });
628
+ if (!scored) return withTag({
629
+ tag,
630
+ tools
631
+ }).slice(0, limit);
632
+ return scored.slice(0, limit).map(({
605
633
  tool
606
634
  }) => {
607
635
  return tool;
@@ -628,39 +656,53 @@ var editDistance = (a, b) => {
628
656
  }
629
657
  return row[b.length];
630
658
  };
631
- /** Names a model probably meant: near-misses first, then related tools. */
659
+ /**
660
+ * Names a model probably meant: close typos first, nearest first; then the
661
+ * near-misses and the tools sharing the name's words, by word score, so a tool
662
+ * holding every word outranks a distant near-miss.
663
+ */
632
664
  var suggestionsFor = ({
633
665
  name,
634
666
  tools
635
667
  }) => {
636
668
  const tolerance = Math.max(2, Math.floor(name.length / 3));
637
- const nearMisses = tools.map(tool => {
669
+ const scores = new Map((scoreTools({
670
+ query: name,
671
+ tools
672
+ }) ?? []).map(({
673
+ tool,
674
+ score
675
+ }) => {
676
+ return [tool, score];
677
+ }));
678
+ const rankOf = distance => {
679
+ return Math.min(distance, 3);
680
+ };
681
+ return tools.map((tool, index) => {
638
682
  return {
639
- name: tool.name,
640
- distance: editDistance(name, tool.name)
683
+ tool,
684
+ index,
685
+ distance: editDistance(name, tool.name),
686
+ score: scores.get(tool) ?? 0
641
687
  };
642
688
  }).filter(({
643
- distance
689
+ distance,
690
+ score
644
691
  }) => {
645
- return distance <= tolerance;
692
+ return distance <= tolerance || score > 0;
646
693
  }).sort((a, b) => {
647
- return a.distance - b.distance;
648
- }).map(candidate => {
649
- return candidate.name;
650
- });
651
- const related = rankTools({
652
- query: name,
653
- limit: SUGGESTION_COUNT,
654
- tools
655
- }).map(tool => {
694
+ return rankOf(a.distance) - rankOf(b.distance) || b.score - a.score || a.distance - b.distance || a.index - b.index;
695
+ }).slice(0, SUGGESTION_COUNT).map(({
696
+ tool
697
+ }) => {
656
698
  return tool.name;
657
699
  });
658
- return [...new Set([...nearMisses, ...related])].slice(0, SUGGESTION_COUNT);
659
700
  };
660
701
 
661
702
  //#endregion
662
703
  //#region src/deferTools.ts
663
704
  var DEFAULT_SEARCH_LIMIT = 10;
705
+ var DEFAULT_SEARCH_MAX_LIMIT = 50;
664
706
  var jsonResult = ({
665
707
  data,
666
708
  isError = false
@@ -705,6 +747,7 @@ var deferredToolsOf = ({
705
747
  tool
706
748
  }) : true;
707
749
  };
750
+ const searchLimit = options.searchLimit ?? DEFAULT_SEARCH_LIMIT;
708
751
  return {
709
752
  names: {
710
753
  search: "search",
@@ -713,7 +756,8 @@ var deferredToolsOf = ({
713
756
  ...options.names
714
757
  },
715
758
  tools,
716
- searchLimit: options.searchLimit ?? DEFAULT_SEARCH_LIMIT,
759
+ searchLimit,
760
+ searchMaxLimit: options.searchMaxLimit ?? Math.max(DEFAULT_SEARCH_MAX_LIMIT, searchLimit),
717
761
  searchTools: options.search ?? rankTools,
718
762
  visibleTools: () => {
719
763
  return tools.filter(isVisible);
@@ -728,6 +772,7 @@ var searchToolOf = ({
728
772
  names,
729
773
  tools,
730
774
  searchLimit,
775
+ searchMaxLimit,
731
776
  searchTools,
732
777
  visibleTools
733
778
  }) => {
@@ -742,7 +787,7 @@ var searchToolOf = ({
742
787
  properties: {
743
788
  query: {
744
789
  type: "string",
745
- description: "Words describing the action, e.g. \"create agent\". Empty lists every tool."
790
+ description: "Words describing the action, e.g. \"create agent\". Empty lists the tools in order."
746
791
  },
747
792
  tag: {
748
793
  type: "string",
@@ -754,6 +799,7 @@ var searchToolOf = ({
754
799
  limit: {
755
800
  type: "integer",
756
801
  minimum: 1,
802
+ maximum: searchMaxLimit,
757
803
  description: `Maximum number of results. Defaults to ${searchLimit}.`
758
804
  }
759
805
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/http-server-mcp",
3
- "version": "0.34.0",
3
+ "version": "0.34.2",
4
4
  "description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
5
5
  "keywords": [
6
6
  "ai",
@@ -39,8 +39,8 @@
39
39
  "zod": "^4.4.3",
40
40
  "@ttoss/auth-core": "^0.16.1",
41
41
  "@ttoss/http-server": "^0.11.1",
42
- "@ttoss/http-server-auth": "^0.8.1",
43
- "@ttoss/i18n-core": "^0.7.0"
42
+ "@ttoss/i18n-core": "^0.7.0",
43
+ "@ttoss/http-server-auth": "^0.8.1"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@modelcontextprotocol/client": "^2.0.0",