Skip to content

Commit 25a88d1

Browse files
authored
Merge pull request #78 from decocms/feat/registry-updates-from-main
Feat/registry updates from main
2 parents 36ed2b5 + d755358 commit 25a88d1

File tree

3 files changed

+140
-4
lines changed

3 files changed

+140
-4
lines changed

registry/server/lib/supabase-client.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,57 @@ export async function upsertServers(
354354
}
355355
}
356356

357+
/**
358+
* Get available tags and categories from all servers
359+
*/
360+
export async function getAvailableFilters(client: SupabaseClient): Promise<{
361+
tags: Array<{ value: string; count: number }>;
362+
categories: Array<{ value: string; count: number }>;
363+
}> {
364+
// Get all latest servers with their tags and categories
365+
const { data, error } = await client
366+
.from("mcp_servers")
367+
.select("tags, categories")
368+
.eq("is_latest", true)
369+
.eq("unlisted", false);
370+
371+
if (error) {
372+
throw new Error(`Error fetching available filters: ${error.message}`);
373+
}
374+
375+
const servers = (data || []) as Array<{
376+
tags: string[] | null;
377+
categories: string[] | null;
378+
}>;
379+
380+
// Count tags
381+
const tagCounts = new Map<string, number>();
382+
servers.forEach((server) => {
383+
server.tags?.forEach((tag) => {
384+
tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
385+
});
386+
});
387+
388+
// Count categories
389+
const categoryCounts = new Map<string, number>();
390+
servers.forEach((server) => {
391+
server.categories?.forEach((category) => {
392+
categoryCounts.set(category, (categoryCounts.get(category) || 0) + 1);
393+
});
394+
});
395+
396+
// Convert to sorted arrays
397+
const tags = Array.from(tagCounts.entries())
398+
.map(([value, count]) => ({ value, count }))
399+
.sort((a, b) => b.count - a.count); // Sort by count desc
400+
401+
const categories = Array.from(categoryCounts.entries())
402+
.map(([value, count]) => ({ value, count }))
403+
.sort((a, b) => b.count - a.count); // Sort by count desc
404+
405+
return { tags, categories };
406+
}
407+
357408
/**
358409
* Get server count by status
359410
*/

registry/server/tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import {
1313
createListRegistryTool,
1414
createGetRegistryTool,
1515
createVersionsRegistryTool,
16+
createFiltersRegistryTool,
1617
} from "./registry-binding.ts";
1718

1819
export const tools = [
1920
createListRegistryTool,
2021
createGetRegistryTool,
2122
createVersionsRegistryTool,
23+
createFiltersRegistryTool,
2224
];

registry/server/tools/registry-binding.ts

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
listServers as listServersFromSupabase,
1515
getServer as getServerFromSupabase,
1616
getServerVersions as getServerVersionsFromSupabase,
17+
getAvailableFilters as getAvailableFiltersFromSupabase,
1718
} from "../lib/supabase-client.ts";
1819

1920
// ============================================================================
@@ -86,10 +87,29 @@ const ListInputSchema = z
8687
.max(100)
8788
.default(30)
8889
.describe("Number of items per page (default: 30)"),
89-
9090
where: WhereSchema.optional().describe(
9191
"Standard WhereExpression filter (converted to simple search internally)",
9292
),
93+
tags: z
94+
.array(z.string())
95+
.optional()
96+
.describe(
97+
"Filter by tags (returns servers that have ANY of the specified tags)",
98+
),
99+
categories: z
100+
.array(z.string())
101+
.optional()
102+
.describe(
103+
"Filter by categories (returns servers that have ANY of the specified categories). Valid categories: productivity, development, data, ai, communication, infrastructure, security, monitoring, analytics, automation",
104+
),
105+
verified: z
106+
.boolean()
107+
.optional()
108+
.describe("Filter by verification status (true = verified only)"),
109+
hasRemote: z
110+
.boolean()
111+
.optional()
112+
.describe("Filter servers that support remote execution"),
93113
})
94114
.describe("Filtering, sorting, and pagination context");
95115

@@ -214,15 +234,23 @@ export const createListRegistryTool = (_env: Env) =>
214234
createPrivateTool({
215235
id: "COLLECTION_REGISTRY_APP_LIST",
216236
description:
217-
"Lists MCP servers available in the registry with support for pagination, search, and boolean filters (has_remotes, has_packages, is_latest, etc.)",
237+
"Lists MCP servers available in the registry with support for pagination, search, and filters (tags, categories, verified, hasRemote). Always returns the latest version of each server.",
218238
inputSchema: ListInputSchema,
219239
outputSchema: ListOutputSchema,
220240
execute: async ({
221241
context,
222242
}: {
223243
context: z.infer<typeof ListInputSchema>;
224244
}) => {
225-
const { limit = 30, cursor, where } = context;
245+
const {
246+
limit = 30,
247+
cursor,
248+
where,
249+
tags,
250+
categories,
251+
verified,
252+
hasRemote,
253+
} = context;
226254
try {
227255
// Get configuration from environment
228256
const supabaseUrl = process.env.SUPABASE_URL;
@@ -245,7 +273,10 @@ export const createListRegistryTool = (_env: Env) =>
245273
limit,
246274
offset,
247275
search: apiSearch,
248-
hasRemote: true, // Only show servers with remotes
276+
tags,
277+
categories,
278+
verified,
279+
hasRemote: hasRemote ?? true, // Default: only show servers with remotes
249280
});
250281

251282
const items = result.servers.map((server) => ({
@@ -391,3 +422,55 @@ export const createVersionsRegistryTool = (_env: Env) =>
391422
}
392423
},
393424
});
425+
426+
/**
427+
* COLLECTION_REGISTRY_APP_FILTERS - Get available filter options
428+
*/
429+
export const createFiltersRegistryTool = (_env: Env) =>
430+
createPrivateTool({
431+
id: "COLLECTION_REGISTRY_APP_FILTERS",
432+
description:
433+
"Gets all available tags and categories that can be used to filter MCP servers, with counts showing how many servers use each filter value",
434+
inputSchema: z.object({}),
435+
outputSchema: z.object({
436+
tags: z
437+
.array(
438+
z.object({
439+
value: z.string().describe("Tag name"),
440+
count: z.number().describe("Number of servers with this tag"),
441+
}),
442+
)
443+
.describe("Available tags sorted by usage count (descending)"),
444+
categories: z
445+
.array(
446+
z.object({
447+
value: z.string().describe("Category name"),
448+
count: z.number().describe("Number of servers in this category"),
449+
}),
450+
)
451+
.describe("Available categories sorted by usage count (descending)"),
452+
}),
453+
execute: async () => {
454+
try {
455+
// Get configuration from environment
456+
const supabaseUrl = process.env.SUPABASE_URL;
457+
const supabaseKey = process.env.SUPABASE_ANON_KEY;
458+
459+
if (!supabaseUrl || !supabaseKey) {
460+
throw new Error(
461+
"Supabase not configured. Please set SUPABASE_URL and SUPABASE_ANON_KEY environment variables.",
462+
);
463+
}
464+
465+
// Query directly from Supabase
466+
const client = createSupabaseClient(supabaseUrl, supabaseKey);
467+
const filters = await getAvailableFiltersFromSupabase(client);
468+
469+
return filters;
470+
} catch (error) {
471+
throw new Error(
472+
`Error getting available filters: ${error instanceof Error ? error.message : "Unknown error"}`,
473+
);
474+
}
475+
},
476+
});

0 commit comments

Comments
 (0)