@@ -25,6 +25,13 @@ interface ServerInfo {
2525 installLink : string ;
2626}
2727
28+ interface ServerGroup {
29+ name : string ;
30+ servers : ServerInfo [ ] ;
31+ }
32+
33+ type IndexEntry = string | [ string , string [ ] ] ;
34+
2835function generateInstallLink (
2936 serverId : string ,
3037 serverConfig : ServerConfig
@@ -69,36 +76,37 @@ function generateInstallLink(
6976 }
7077}
7178
79+ async function loadServerInfo (
80+ serverId : string ,
81+ serversDir : string
82+ ) : Promise < ServerInfo | null > {
83+ try {
84+ const serverConfigPath = path . join ( serversDir , serverId , "server.json" ) ;
85+ const serverConfigFile = file ( serverConfigPath ) ;
86+ const serverConfig : ServerConfig = JSON . parse (
87+ await serverConfigFile . text ( )
88+ ) ;
89+
90+ return {
91+ id : serverId ,
92+ name : serverConfig . name ,
93+ description : serverConfig . description ,
94+ installLink : generateInstallLink ( serverId , serverConfig ) ,
95+ } ;
96+ } catch ( error ) {
97+ console . warn ( `Failed to read config for ${ serverId } :` , error ) ;
98+ return null ;
99+ }
100+ }
101+
72102async function generateReadme ( ) : Promise < void > {
73103 const rootDir = path . resolve ( import . meta. dir , ".." ) ;
74104 const serversDir = path . join ( rootDir , "servers" ) ;
75105
76- // Read the index.json to get the ordered list of servers
106+ // Read the index.json to get the ordered list of servers and groups
77107 const indexPath = path . join ( serversDir , "index.json" ) ;
78108 const indexFile = file ( indexPath ) ;
79- const serverIds : string [ ] = JSON . parse ( await indexFile . text ( ) ) ;
80-
81- // Read each server's configuration
82- const servers : ServerInfo [ ] = [ ] ;
83-
84- for ( const serverId of serverIds ) {
85- try {
86- const serverConfigPath = path . join ( serversDir , serverId , "server.json" ) ;
87- const serverConfigFile = file ( serverConfigPath ) ;
88- const serverConfig : ServerConfig = JSON . parse (
89- await serverConfigFile . text ( )
90- ) ;
91-
92- servers . push ( {
93- id : serverId ,
94- name : serverConfig . name ,
95- description : serverConfig . description ,
96- installLink : generateInstallLink ( serverId , serverConfig ) ,
97- } ) ;
98- } catch ( error ) {
99- console . warn ( `Failed to read config for ${ serverId } :` , error ) ;
100- }
101- }
109+ const indexEntries : IndexEntry [ ] = JSON . parse ( await indexFile . text ( ) ) ;
102110
103111 // Generate the README content
104112 let readmeContent = `# MCP Servers
@@ -111,13 +119,52 @@ To add a server, see the [Contributing Guidelines](CONTRIBUTING.md).
111119|--------|-------------|---------|
112120` ;
113121
114- // Add each server to the table
115- for ( const server of servers ) {
116- const installButton = server . installLink
117- ? `<a href="${ server . installLink } " style="border: 1px solid rgba(128, 128, 128, 0.5); padding: 4px 8px; text-decoration: none; border-radius: 4px; font-size: 12px;">Install</a>`
118- : "" ;
122+ let standaloneCount = 0 ;
123+ let groupCount = 0 ;
124+
125+ // Process each entry in order and render inline
126+ for ( const entry of indexEntries ) {
127+ if ( typeof entry === "string" ) {
128+ // Standalone server
129+ const serverInfo = await loadServerInfo ( entry , serversDir ) ;
130+ if ( serverInfo ) {
131+ const installButton = serverInfo . installLink
132+ ? `<a href="${ serverInfo . installLink } " style="border: 1px solid rgba(128, 128, 128, 0.5); padding: 4px 8px; text-decoration: none; border-radius: 4px; font-size: 12px;">Install</a>`
133+ : "" ;
134+
135+ readmeContent += `| **${ serverInfo . name } ** | ${ serverInfo . description } | ${ installButton } |\n` ;
136+ standaloneCount ++ ;
137+ }
138+ } else if ( Array . isArray ( entry ) && entry . length === 2 ) {
139+ // Group: [groupName, [serverId1, serverId2, ...]]
140+ const [ groupName , serverIds ] = entry ;
141+ const groupServers : ServerInfo [ ] = [ ] ;
142+
143+ for ( const serverId of serverIds ) {
144+ const serverInfo = await loadServerInfo ( serverId , serversDir ) ;
145+ if ( serverInfo ) {
146+ groupServers . push ( serverInfo ) ;
147+ }
148+ }
119149
120- readmeContent += `| **${ server . name } ** | ${ server . description } | ${ installButton } |\n` ;
150+ if ( groupServers . length > 0 ) {
151+ // Build a simple list of servers for the accordion content
152+ // Use <br> tags instead of newlines to keep everything on one line for markdown table compatibility
153+ let serverList = '' ;
154+ for ( const server of groupServers ) {
155+ const installButton = server . installLink
156+ ? ` <a href="${ server . installLink } " style="border: 1px solid rgba(128, 128, 128, 0.5); padding: 4px 8px; text-decoration: none; border-radius: 4px; font-size: 12px;">Install</a>`
157+ : "" ;
158+ serverList += `<br>- **${ server . name } ** - ${ server . description } ${ installButton } ` ;
159+ }
160+
161+ // Render group as a table row with accordion in Description column
162+ // Keep everything on one line to avoid breaking markdown table parsing
163+ const detailsContent = `<details><summary>${ groupServers . length } server${ groupServers . length > 1 ? 's' : '' } </summary>${ serverList } </details>` ;
164+ readmeContent += `| **${ groupName } ** | ${ detailsContent } | - |\n` ;
165+ groupCount ++ ;
166+ }
167+ }
121168 }
122169
123170 readmeContent += `
@@ -130,7 +177,8 @@ Each server has its own configuration requirements. Refer to the individual serv
130177 const readmePath = path . join ( rootDir , "README.md" ) ;
131178 await Bun . write ( readmePath , readmeContent ) ;
132179
133- console . log ( `README.md updated with ${ servers . length } servers` ) ;
180+ const totalServers = standaloneCount + groupCount ;
181+ console . log ( `README.md updated with ${ totalServers } entries (${ standaloneCount } standalone servers, ${ groupCount } groups)` ) ;
134182}
135183
136184if ( import . meta. main ) {
0 commit comments