@webmcp-auto-ui/agent 2.5.11 → 2.5.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmcp-auto-ui/agent",
3
- "version": "2.5.11",
3
+ "version": "2.5.12",
4
4
  "description": "LLM agent loop + Anthropic/Gemma LiteRT providers + MCP wrapper",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -41,6 +41,39 @@ export class DiscoveryCache {
41
41
  this.servers.clear();
42
42
  }
43
43
 
44
+ /** All registered server prefixes */
45
+ serverPrefixes(): string[] {
46
+ return [...this.servers.keys()];
47
+ }
48
+
49
+ /** All recipes across all servers, tagged with their server prefix */
50
+ allRecipes(): Array<CachedRecipe & { server: string }> {
51
+ const result: Array<CachedRecipe & { server: string }> = [];
52
+ for (const [prefix, cache] of this.servers) {
53
+ for (const r of cache.recipes) result.push({ ...r, server: prefix });
54
+ }
55
+ return result;
56
+ }
57
+
58
+ /** All tools across all servers, tagged with their server prefix */
59
+ allTools(): Array<{ name: string; description?: string; inputSchema?: Record<string, unknown>; server: string }> {
60
+ const result: Array<{ name: string; description?: string; inputSchema?: Record<string, unknown>; server: string }> = [];
61
+ for (const [prefix, cache] of this.servers) {
62
+ for (const t of cache.tools) result.push({ ...t, server: prefix });
63
+ }
64
+ return result;
65
+ }
66
+
67
+ /** Recipe count for a specific server */
68
+ recipeCount(serverPrefix: string): number {
69
+ return this.servers.get(serverPrefix)?.recipes.length ?? 0;
70
+ }
71
+
72
+ /** Tool count for a specific server */
73
+ toolCount(serverPrefix: string): number {
74
+ return this.servers.get(serverPrefix)?.tools.length ?? 0;
75
+ }
76
+
44
77
  /**
45
78
  * Try to resolve a discovery tool call from cache.
46
79
  * Returns the result string if handled, or null if not a discovery tool.
package/src/index.ts CHANGED
@@ -38,6 +38,11 @@ export type { WebMcpServer, WebMcpToolDef, WidgetEntry } from '@webmcp-auto-ui/c
38
38
  // Recipes
39
39
  export { WEBMCP_RECIPES, parseRecipe, parseRecipes } from './recipes/index.js';
40
40
  export { recipeRegistry, registerRecipes, filterRecipesByServer, formatRecipesForPrompt, formatMcpRecipesForPrompt } from './recipe-registry.js';
41
+ export { filterRecipes, sortRecipes, recipeToMarkdown, recipeToDownloadBlob } from './recipe-browser.js';
42
+
43
+ // Tool browser
44
+ export { groupToolsByServer, formatToolSchema } from './tool-browser.js';
45
+ export type { BrowsableTool } from './tool-browser.js';
41
46
 
42
47
  // Summarize
43
48
  export { summarizeChat } from './summarize.js';
@@ -0,0 +1,85 @@
1
+ // recipe-browser — pure utility functions for browsing/filtering/exporting recipes
2
+
3
+ /**
4
+ * Case-insensitive filter on name and description fields.
5
+ * Empty query returns all recipes.
6
+ */
7
+ export function filterRecipes<T extends { name: string; description?: string }>(
8
+ recipes: T[],
9
+ query: string,
10
+ ): T[] {
11
+ const q = query.trim().toLowerCase();
12
+ if (!q) return recipes;
13
+ return recipes.filter(
14
+ (r) =>
15
+ r.name.toLowerCase().includes(q) ||
16
+ (r.description && r.description.toLowerCase().includes(q)),
17
+ );
18
+ }
19
+
20
+ /**
21
+ * Returns a new array sorted alphabetically by name (case-insensitive).
22
+ * Does NOT mutate the input array.
23
+ */
24
+ export function sortRecipes<T extends { name: string }>(recipes: T[]): T[] {
25
+ return [...recipes].sort((a, b) =>
26
+ a.name.toLowerCase().localeCompare(b.name.toLowerCase()),
27
+ );
28
+ }
29
+
30
+ // Keys that belong in Recipe frontmatter (order matters for readability)
31
+ const RECIPE_FM_KEYS = ['id', 'name', 'description', 'when', 'components_used', 'servers', 'layout'] as const;
32
+ // Keys for McpRecipe frontmatter
33
+ const MCP_FM_KEYS = ['name', 'description'] as const;
34
+
35
+ function yamlValue(value: unknown): string {
36
+ if (value === null || value === undefined) return '""';
37
+ if (typeof value === 'string') return value.includes(':') || value.includes('#') || value.includes('\n') ? JSON.stringify(value) : value;
38
+ if (Array.isArray(value)) return `[${value.map((v) => JSON.stringify(v)).join(', ')}]`;
39
+ if (typeof value === 'object') return JSON.stringify(value);
40
+ return String(value);
41
+ }
42
+
43
+ /**
44
+ * Reconstructs a markdown file from a recipe object.
45
+ * Handles both Recipe (id, when, components_used, servers, layout, body)
46
+ * and McpRecipe (name, description, body?) shapes.
47
+ */
48
+ export function recipeToMarkdown(recipe: Record<string, unknown>): string {
49
+ const isFullRecipe = 'id' in recipe || 'when' in recipe;
50
+ const fmKeys = isFullRecipe ? RECIPE_FM_KEYS : MCP_FM_KEYS;
51
+
52
+ const lines: string[] = ['---'];
53
+ for (const key of fmKeys) {
54
+ if (key in recipe && recipe[key] !== undefined) {
55
+ lines.push(`${key}: ${yamlValue(recipe[key])}`);
56
+ }
57
+ }
58
+ lines.push('---');
59
+
60
+ const body = typeof recipe.body === 'string' ? recipe.body.trim() : '';
61
+ if (body) {
62
+ lines.push('', body);
63
+ }
64
+
65
+ return lines.join('\n') + '\n';
66
+ }
67
+
68
+ function sanitizeFilename(raw: string): string {
69
+ return raw.replace(/\s+/g, '-').toLowerCase().replace(/[^a-z0-9_.-]/g, '');
70
+ }
71
+
72
+ /**
73
+ * Creates a downloadable Blob from a recipe object.
74
+ * Filename is derived from name or id, sanitized.
75
+ */
76
+ export function recipeToDownloadBlob(
77
+ recipe: Record<string, unknown>,
78
+ ): { blob: Blob; filename: string } {
79
+ const md = recipeToMarkdown(recipe);
80
+ const rawName = (typeof recipe.name === 'string' && recipe.name)
81
+ || (typeof recipe.id === 'string' && recipe.id)
82
+ || 'recipe';
83
+ const filename = sanitizeFilename(rawName) + '.md';
84
+ return { blob: new Blob([md], { type: 'text/markdown' }), filename };
85
+ }
@@ -0,0 +1,36 @@
1
+ // tool-browser — pure utility functions for browsing/filtering tools
2
+
3
+ export interface BrowsableTool {
4
+ name: string;
5
+ description?: string;
6
+ server?: string;
7
+ inputSchema?: Record<string, unknown>;
8
+ }
9
+
10
+ /**
11
+ * Group tools by server name.
12
+ * Returns a Map<serverName, tools[]> with alphabetically sorted tools in each group.
13
+ */
14
+ export function groupToolsByServer(tools: BrowsableTool[]): Map<string, BrowsableTool[]> {
15
+ const map = new Map<string, BrowsableTool[]>();
16
+ for (const t of tools) {
17
+ const key = t.server || 'Unknown';
18
+ const arr = map.get(key) ?? [];
19
+ arr.push(t);
20
+ map.set(key, arr);
21
+ }
22
+ // Sort tools within each group
23
+ for (const [key, arr] of map) {
24
+ map.set(key, arr.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())));
25
+ }
26
+ return map;
27
+ }
28
+
29
+ /**
30
+ * Format a tool's input schema as a readable string.
31
+ * Returns pretty-printed JSON, or null if no schema.
32
+ */
33
+ export function formatToolSchema(tool: BrowsableTool): string | null {
34
+ if (!tool.inputSchema) return null;
35
+ return JSON.stringify(tool.inputSchema, null, 2);
36
+ }