Skip to content

Commit 92afca1

Browse files
author
Lasim
committed
feat(frontend): add source filter to MCP server catalog
feat(frontend): implement search functionality with loading state feat(frontend): enhance input group components for better UI feat(frontend): update translations for source filter feat(backend): add source field to MCP server API specifications
1 parent a75e485 commit 92afca1

File tree

13 files changed

+530
-264
lines changed

13 files changed

+530
-264
lines changed

services/backend/api-spec.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16320,6 +16320,14 @@
1632016320
"nullable": true,
1632116321
"description": "Official registry name"
1632216322
},
16323+
"source": {
16324+
"type": "string",
16325+
"enum": [
16326+
"official_registry",
16327+
"manual"
16328+
],
16329+
"description": "Source of the MCP server"
16330+
},
1632316331
"created_at": {
1632416332
"type": "string",
1632516333
"format": "date-time",
@@ -16343,6 +16351,7 @@
1634316351
"status",
1634416352
"featured",
1634516353
"requires_oauth",
16354+
"source",
1634616355
"created_at",
1634716356
"updated_at"
1634816357
]
@@ -17077,6 +17086,19 @@
1707717086
"required": false,
1707817087
"description": "Filter by server status"
1707917088
},
17089+
{
17090+
"schema": {
17091+
"type": "string",
17092+
"enum": [
17093+
"official_registry",
17094+
"manual"
17095+
]
17096+
},
17097+
"in": "query",
17098+
"name": "source",
17099+
"required": false,
17100+
"description": "Filter by server source: official_registry or manual"
17101+
},
1708017102
{
1708117103
"schema": {
1708217104
"type": "string",
@@ -17292,6 +17314,14 @@
1729217314
"nullable": true,
1729317315
"description": "Official registry name"
1729417316
},
17317+
"source": {
17318+
"type": "string",
17319+
"enum": [
17320+
"official_registry",
17321+
"manual"
17322+
],
17323+
"description": "Source of the MCP server"
17324+
},
1729517325
"created_at": {
1729617326
"type": "string",
1729717327
"format": "date-time",
@@ -17315,6 +17345,7 @@
1731517345
"status",
1731617346
"featured",
1731717347
"requires_oauth",
17348+
"source",
1731817349
"created_at",
1731917350
"updated_at"
1732017351
]

services/backend/api-spec.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11374,6 +11374,12 @@ paths:
1137411374
type: string
1137511375
nullable: true
1137611376
description: Official registry name
11377+
source:
11378+
type: string
11379+
enum:
11380+
- official_registry
11381+
- manual
11382+
description: Source of the MCP server
1137711383
created_at:
1137811384
type: string
1137911385
format: date-time
@@ -11394,6 +11400,7 @@ paths:
1139411400
- status
1139511401
- featured
1139611402
- requires_oauth
11403+
- source
1139711404
- created_at
1139811405
- updated_at
1139911406
description: Array of server objects (minimal fields for listing)
@@ -11929,6 +11936,15 @@ paths:
1192911936
name: status
1193011937
required: false
1193111938
description: Filter by server status
11939+
- schema:
11940+
type: string
11941+
enum:
11942+
- official_registry
11943+
- manual
11944+
in: query
11945+
name: source
11946+
required: false
11947+
description: "Filter by server source: official_registry or manual"
1193211948
- schema:
1193311949
type: string
1193411950
enum:
@@ -12093,6 +12109,12 @@ paths:
1209312109
type: string
1209412110
nullable: true
1209512111
description: Official registry name
12112+
source:
12113+
type: string
12114+
enum:
12115+
- official_registry
12116+
- manual
12117+
description: Source of the MCP server
1209612118
created_at:
1209712119
type: string
1209812120
format: date-time
@@ -12113,6 +12135,7 @@ paths:
1211312135
- status
1211412136
- featured
1211512137
- requires_oauth
12138+
- source
1211612139
- created_at
1211712140
- updated_at
1211812141
description: Array of server objects (minimal fields for listing)

services/backend/src/routes/mcp/servers/schemas.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,12 @@ export const SEARCH_SERVERS_QUERY_SCHEMA = {
113113
enum: MCP_SERVER_STATUS_VALUES as unknown as string[],
114114
description: 'Filter by server status'
115115
},
116-
featured: {
116+
source: {
117+
type: 'string',
118+
enum: ['official_registry', 'manual'],
119+
description: 'Filter by server source: official_registry or manual'
120+
},
121+
featured: {
117122
type: 'string',
118123
enum: ['true', 'false'],
119124
description: 'Filter by featured status: true for featured servers, false for non-featured servers'
@@ -1184,10 +1189,11 @@ export const SERVER_LIST_ENTITY_SCHEMA = {
11841189
author_name: { type: 'string', nullable: true, description: 'Author name' },
11851190
organization: { type: 'string', nullable: true, description: 'Organization' },
11861191
official_name: { type: 'string', nullable: true, description: 'Official registry name' },
1192+
source: { type: 'string', enum: ['official_registry', 'manual'], description: 'Source of the MCP server' },
11871193
created_at: { type: 'string', format: 'date-time', description: 'Creation timestamp' },
11881194
updated_at: { type: 'string', format: 'date-time', description: 'Last update timestamp' }
11891195
},
1190-
required: ['id', 'name', 'slug', 'description', 'language', 'runtime', 'transport_type', 'visibility', 'status', 'featured', 'requires_oauth', 'created_at', 'updated_at']
1196+
required: ['id', 'name', 'slug', 'description', 'language', 'runtime', 'transport_type', 'visibility', 'status', 'featured', 'requires_oauth', 'source', 'created_at', 'updated_at']
11911197
} as const;
11921198

11931199
export const LIST_SERVERS_SUCCESS_RESPONSE_SCHEMA = {
@@ -1775,6 +1781,7 @@ export interface ServerListEntity {
17751781
author_name: string | null;
17761782
organization: string | null;
17771783
official_name: string | null;
1784+
source: 'official_registry' | 'manual';
17781785
created_at: string;
17791786
updated_at: string;
17801787
}
@@ -1972,6 +1979,7 @@ export function formatServerListResponse(
19721979
author_name: server.author_name || null,
19731980
organization: server.organization || null,
19741981
official_name: server.official_name || null,
1982+
source: server.source || 'manual',
19751983
created_at: server.created_at.toISOString(),
19761984
updated_at: server.updated_at.toISOString()
19771985
};

services/backend/src/routes/mcp/servers/search.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface SearchServersQueryParams extends Omit<ListServersQueryParams, 'search'
1919
q: string; // Search query is required for search endpoint
2020
tags?: string; // Optional comma-separated tags filter
2121
sort_by?: 'name' | 'github_stars'; // Optional sort parameter
22+
source?: 'official_registry' | 'manual'; // Optional source filter
2223
}
2324

2425
export default async function searchServers(server: FastifyInstance) {
@@ -63,6 +64,7 @@ export default async function searchServers(server: FastifyInstance) {
6364
language: queryParams.language,
6465
runtime: queryParams.runtime,
6566
status: queryParams.status,
67+
source: queryParams.source,
6668
featured: queryParams.featured,
6769
tags: queryParams.tags
6870
},
@@ -112,7 +114,8 @@ export default async function searchServers(server: FastifyInstance) {
112114
runtime: queryParams.runtime,
113115
status: status,
114116
featured: featured,
115-
tags: queryParams.tags
117+
tags: queryParams.tags,
118+
source: queryParams.source
116119
};
117120

118121
// Get servers using the service (which handles permission filtering)

services/frontend/src/components/mcp-server/CategoryDisplay.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,15 @@ const eventBus = useEventBus()
1212
// Storage key for categories cache
1313
const CATEGORIES_STORAGE_KEY = 'mcp_categories_cache'
1414
15+
interface CategoryData {
16+
id: string
17+
name: string
18+
icon?: string | null
19+
}
20+
1521
interface Props {
1622
categoryId?: string | null
17-
category?: McpCategory | null
23+
category?: CategoryData | McpCategory | null
1824
showNotProvided?: boolean
1925
iconClass?: string
2026
textClass?: string
@@ -30,7 +36,7 @@ const props = withDefaults(defineProps<Props>(), {
3036
containerClass: 'flex items-center gap-2'
3137
})
3238
33-
const category = ref<McpCategory | null>(props.category)
39+
const category = ref<CategoryData | McpCategory | null>(props.category)
3440
const isLoading = ref(false)
3541
const error = ref<string | null>(null)
3642
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from 'vue'
3+
import { cn } from '@/lib/utils'
4+
5+
const props = defineProps<{
6+
class?: HTMLAttributes['class']
7+
disabled?: boolean
8+
}>()
9+
</script>
10+
11+
<template>
12+
<div
13+
:class="cn(
14+
'flex items-center rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow]',
15+
'focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]',
16+
'has-[input:disabled]:opacity-50 has-[input:disabled]:cursor-not-allowed',
17+
props.class
18+
)"
19+
:data-disabled="disabled || undefined"
20+
>
21+
<slot />
22+
</div>
23+
</template>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from 'vue'
3+
import { cn } from '@/lib/utils'
4+
5+
const props = defineProps<{
6+
class?: HTMLAttributes['class']
7+
align?: 'start' | 'end' | 'inline-start' | 'inline-end'
8+
}>()
9+
10+
const alignmentClasses = {
11+
'start': 'order-first pl-3',
12+
'end': 'order-last pr-3',
13+
'inline-start': 'order-first pl-3 border-r border-input bg-muted/50',
14+
'inline-end': 'order-last pr-3 border-l border-input bg-muted/50',
15+
}
16+
</script>
17+
18+
<template>
19+
<div
20+
:class="cn(
21+
'flex items-center justify-center text-muted-foreground [&>svg]:size-4',
22+
alignmentClasses[align || 'end'],
23+
props.class
24+
)"
25+
>
26+
<slot />
27+
</div>
28+
</template>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from 'vue'
3+
import { useVModel } from '@vueuse/core'
4+
import { cn } from '@/lib/utils'
5+
6+
const props = defineProps<{
7+
defaultValue?: string | number
8+
modelValue?: string | number
9+
class?: HTMLAttributes['class']
10+
placeholder?: string
11+
disabled?: boolean
12+
type?: string
13+
}>()
14+
15+
const emits = defineEmits<{
16+
(e: 'update:modelValue', payload: string | number): void
17+
}>()
18+
19+
const modelValue = useVModel(props, 'modelValue', emits, {
20+
passive: true,
21+
defaultValue: props.defaultValue,
22+
})
23+
</script>
24+
25+
<template>
26+
<input
27+
v-model="modelValue"
28+
:type="type || 'text'"
29+
:placeholder="placeholder"
30+
:disabled="disabled"
31+
:class="cn(
32+
'flex-1 h-9 min-w-0 bg-transparent px-3 py-1 text-base outline-none placeholder:text-muted-foreground md:text-sm',
33+
'disabled:pointer-events-none disabled:cursor-not-allowed',
34+
props.class
35+
)"
36+
>
37+
</template>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { default as InputGroup } from './InputGroup.vue'
2+
export { default as InputGroupInput } from './InputGroupInput.vue'
3+
export { default as InputGroupAddon } from './InputGroupAddon.vue'

services/frontend/src/i18n/locales/en/mcp-catalog.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export default {
1111
noCategory: 'No category assigned',
1212
openMenu: 'Open menu',
1313
search: {
14-
placeholder: 'Search servers by name, description, or tags...',
14+
placeholder: 'Search MCP Server',
15+
searching: 'Searching...',
1516
button: 'Search'
1617
},
1718
columns: {
@@ -20,6 +21,7 @@ export default {
2021
language: 'Language',
2122
runtime: 'Runtime',
2223
category: 'Category',
24+
source: 'Source',
2325
status: 'Status',
2426
featured: 'Featured',
2527
created: 'Created',
@@ -860,6 +862,7 @@ export default {
860862
},
861863

862864
filters: {
865+
button: 'Filters',
863866
clear: 'Clear Filters',
864867
activeSearch: 'Search active - {count} results',
865868
filtersApplied: 'Filters applied - {count} results',
@@ -870,6 +873,7 @@ export default {
870873
},
871874
status: {
872875
label: 'Status',
876+
placeholder: 'All',
873877
all: 'All Statuses',
874878
active: 'Active',
875879
deprecated: 'Deprecated',
@@ -878,23 +882,27 @@ export default {
878882
},
879883
language: {
880884
label: 'Language',
885+
placeholder: 'All',
881886
all: 'All Languages'
882887
},
883888
runtime: {
884889
label: 'Runtime',
890+
placeholder: 'All',
885891
all: 'All Runtimes'
886892
},
887893
featured: {
888894
label: 'Featured',
895+
placeholder: 'All',
889896
all: 'All Servers',
890-
yes: 'Featured Only',
891-
no: 'Non-Featured'
897+
yes: 'Yes',
898+
no: 'No'
892899
},
893900
autoInstall: {
894901
label: 'Auto Install',
902+
placeholder: 'All',
895903
all: 'All Servers',
896-
yes: 'Auto Install Enabled',
897-
no: 'Auto Install Disabled'
904+
yes: 'Yes',
905+
no: 'No'
898906
}
899907
},
900908

0 commit comments

Comments
 (0)