@@ -3,8 +3,8 @@ import { z } from 'zod';
33import { zodToJsonSchema } from 'zod-to-json-schema' ;
44import { McpCatalogService } from '../../../services/mcpCatalogService' ;
55import { TeamService } from '../../../services/teamService' ;
6+ import { getUserRole , requirePermission } from '../../../middleware/roleMiddleware' ;
67import { getDb } from '../../../db' ;
7- import { getUserRole } from '../../../middleware/roleMiddleware' ;
88
99// Query parameters schema
1010const querySchema = z . object ( {
@@ -70,6 +70,7 @@ const errorResponseSchema = z.object({
7070
7171export default async function listServers ( server : FastifyInstance ) {
7272 server . get ( '/mcp/servers' , {
73+ preValidation : requirePermission ( 'mcp.servers.read' ) ,
7374 schema : {
7475 tags : [ 'MCP Servers' ] ,
7576 summary : 'List MCP servers' ,
@@ -94,119 +95,97 @@ export default async function listServers(server: FastifyInstance) {
9495 } )
9596 }
9697 } ,
97- preValidation : async ( request , reply ) => {
98- // Require authentication for all MCP server access
99- if ( ! request . user ) {
100- return reply . status ( 401 ) . send ( {
101- success : false ,
102- error : 'Authentication required'
103- } ) ;
104- }
105- }
98+ validatorCompiler : ( ) => ( ) => true , // Disable validation but keep schema for docs
99+ serializerCompiler : ( ) => ( data ) => JSON . stringify ( data ) // Disable response validation
106100 } , async ( request , reply ) => {
107101 try {
108102 const db = getDb ( ) ;
109- const catalogService = new McpCatalogService ( db , server . log ) ;
110-
111- // Parse query parameters
112- const filters = querySchema . parse ( request . query ) ;
103+ const catalogService = new McpCatalogService ( db , request . log ) ;
113104
114- // Get user info from authenticated request
115- const userId = request . user ! . id ;
116- const userRoleData = await getUserRole ( userId ) ;
117- const userRole = userRoleData ?. id || 'global_user' ;
105+ // Get user role and team memberships (same as search endpoint)
106+ const roleInfo = await getUserRole ( request . user ! . id ) ;
107+ const userRole = roleInfo ?. id || 'global_user' ;
118108
119109 // Get user's team memberships
120110 let teamIds : string [ ] = [ ] ;
121111 try {
122- const userTeams = await TeamService . getUserTeams ( userId ) ;
112+ const userTeams = await TeamService . getUserTeams ( request . user ! . id ) ;
123113 // eslint-disable-next-line @typescript-eslint/no-explicit-any
124114 teamIds = userTeams . map ( ( team : any ) => team . id ) ;
125115 } catch ( teamError ) {
126- server . log . warn ( {
116+ request . log . warn ( {
127117 operation : 'list_mcp_servers' ,
128- userId,
118+ userId : request . user ! . id ,
129119 teamError
130120 } , 'Failed to get user teams, continuing with empty team list' ) ;
131121 teamIds = [ ] ;
132122 }
133-
134- // Extract pagination parameters from filters
135- const { limit, offset, ...serverFilters } = filters ;
136-
137- const allServers = await catalogService . getServersForUser ( userId , userRole , teamIds , serverFilters ) ;
138123
124+ // Get servers using the service (which handles permission filtering)
125+ const allServers = await catalogService . getServersForUser (
126+ request . user ! . id ,
127+ userRole ,
128+ teamIds ,
129+ { } // No filters for now
130+ ) ;
131+
132+ // Parse query parameters for pagination
133+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
134+ const query = request . query as any ;
135+ const limit = parseInt ( query . limit ) || 20 ;
136+ const offset = parseInt ( query . offset ) || 0 ;
137+
139138 // Apply pagination
140139 const total = allServers . length ;
141140 const paginatedServers = allServers . slice ( offset , offset + limit ) ;
142141
143- server . log . info ( {
142+ request . log . info ( {
144143 operation : 'list_mcp_servers' ,
145- userId,
144+ userId : request . user ! . id ,
146145 totalResults : total ,
147146 returnedResults : paginatedServers . length ,
148147 userRole,
149- teamCount : teamIds . length ,
150- pagination : { limit, offset }
148+ teamCount : teamIds . length
151149 } , 'MCP server list completed' ) ;
152150
153- // eslint-disable-next-line @typescript-eslint/no-explicit-any
154- const safeJsonParse = ( value : any , fallback : any = null ) => {
155- if ( ! value || value === 'null' || value === 'undefined' ) {
156- return fallback ;
157- }
158-
159- if ( typeof value === 'object' ) {
160- return value ;
161- }
162-
163- if ( typeof value === 'string' ) {
164- // Handle the case where objects were stringified incorrectly as "[object Object],[object Object]"
165- if ( value . includes ( '[object Object]' ) ) {
166- server . log . warn ( { value } , 'Detected malformed object string, returning fallback' ) ;
167- return fallback ;
168- }
169-
170- // First try JSON parsing
151+ // Format dates for response (same as search endpoint)
152+ const responseServers = paginatedServers . map ( server => {
153+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
154+ const formatDate = ( date : any ) => {
155+ if ( ! date ) return null ;
171156 try {
172- return JSON . parse ( value ) ;
173- } catch ( error ) {
174- // If JSON parsing fails, check if it's a comma-separated string (for tags)
175- if ( value . includes ( ',' ) && ! value . startsWith ( '[' ) && ! value . startsWith ( '{' ) ) {
176- // Split by comma and trim whitespace, filter out empty and malformed entries
177- const items = value . split ( ',' )
178- . map ( ( item : string ) => item . trim ( ) )
179- . filter ( ( item : string ) => item . length > 0 && ! item . includes ( '[object Object]' ) ) ;
180-
181- return items . length > 0 ? items : fallback ;
157+ // Handle both Date objects and timestamp numbers
158+ if ( typeof date === 'number' ) {
159+ return new Date ( date ) . toISOString ( ) ;
160+ }
161+ if ( date instanceof Date ) {
162+ return date . toISOString ( ) ;
182163 }
183- server . log . warn ( { value, error } , 'Failed to parse JSON field' ) ;
184- return fallback ;
164+ return new Date ( date ) . toISOString ( ) ;
165+ } catch ( error ) {
166+ request . log . warn ( {
167+ operation : 'list_mcp_servers' ,
168+ serverId : server . id ,
169+ field : 'date_format_error' ,
170+ dateValue : date ,
171+ error
172+ } , 'Failed to format date field, using null' ) ;
173+ return null ;
185174 }
186- }
187-
188- return fallback ;
189- } ;
175+ } ;
176+
177+ return {
178+ ...server ,
179+ created_at : formatDate ( server . created_at ) ,
180+ updated_at : formatDate ( server . updated_at ) ,
181+ last_sync_at : formatDate ( server . last_sync_at )
182+ } ;
183+ } ) ;
190184
185+ // Return the format your frontend expects
191186 return reply . send ( {
192- success : true ,
193187 data : {
194- servers : paginatedServers . map ( server => ( {
195- ...server ,
196- // Parse JSON fields for proper typing with error handling
197- tags : safeJsonParse ( server . tags , null ) ,
198- installation_methods : safeJsonParse ( server . installation_methods , [ ] ) ,
199- tools : safeJsonParse ( server . tools , [ ] ) ,
200- resources : safeJsonParse ( server . resources , null ) ,
201- prompts : safeJsonParse ( server . prompts , null ) ,
202- environment_variables : safeJsonParse ( server . environment_variables , null ) ,
203- default_config : safeJsonParse ( server . default_config , null ) ,
204- dependencies : safeJsonParse ( server . dependencies , null ) ,
205- // Convert dates to ISO strings
206- created_at : server . created_at . toISOString ( ) ,
207- updated_at : server . updated_at . toISOString ( ) ,
208- last_sync_at : server . last_sync_at ?. toISOString ( ) || null
209- } ) ) ,
188+ servers : responseServers ,
210189 pagination : {
211190 total,
212191 limit,
@@ -215,16 +194,18 @@ export default async function listServers(server: FastifyInstance) {
215194 }
216195 }
217196 } ) ;
218- } catch ( error ) {
219- server . log . error ( {
197+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
198+ } catch ( error : any ) {
199+ request . log . error ( {
220200 operation : 'list_servers' ,
221201 userId : request . user ?. id ,
222- error
202+ error : error . message || error ,
203+ stack : error . stack
223204 } , 'Failed to list MCP servers' ) ;
224205
225206 return reply . status ( 500 ) . send ( {
226207 success : false ,
227- error : 'Failed to retrieve servers'
208+ error : error . message || 'Failed to retrieve servers'
228209 } ) ;
229210 }
230211 } ) ;
0 commit comments