284284 flex-direction : column;
285285 border : 1px solid var (--border );
286286 cursor : pointer;
287+ position : relative;
287288 }
288289
289290 .server-card : hover {
392393 margin-top : auto;
393394 margin-bottom : 0 ;
394395 }
396+
397+ .install-button {
398+ background-color : var (--button-bg );
399+ color : white;
400+ border : none;
401+ border-radius : 4px ;
402+ padding : 6px 12px ;
403+ font-size : 0.8rem ;
404+ cursor : pointer;
405+ display : flex;
406+ align-items : center;
407+ gap : 4px ;
408+ transition : background-color 0.2s ;
409+ text-decoration : none;
410+ width : fit-content;
411+ position : absolute;
412+ bottom : 10px ;
413+ right : 1.25rem ;
414+ z-index : 10 ;
415+ }
416+
417+ .install-button : hover {
418+ background-color : var (--button-hover );
419+ }
420+
421+ .install-button svg {
422+ width : 14px ;
423+ height : 14px ;
424+ }
395425
396426 .server-card .meta .author {
397427 color : var (--accent-highlight );
@@ -1473,13 +1503,48 @@ <h3>Raw Manifest:</h3>
14731503 </ div >
14741504 </ div >
14751505
1506+ <!-- Load Lucien servers configuration -->
1507+
14761508 < script >
1509+ { % include lucien - servers . js % }
14771510 // Server data - to be loaded from the combined JSON file (as a map with server names as keys)
14781511 let serversMap = { } ;
14791512 let serversArray = [ ] ; // Also keep an array version for rendering
14801513
14811514 // Fetch all servers data
14821515 const serverDataUrl = '/api/servers.json' ;
1516+ const searchParams = new URLSearchParams ( window . location . search ) ;
1517+ const sortRule = {
1518+ 'isPinned' : 0 ,
1519+ 'isOfficial' : 1 ,
1520+ 'others' : 2 ,
1521+ }
1522+ const isLucien = searchParams . get ( 'source' ) === 'lucien'
1523+ const sourceProtocol = searchParams . get ( 'protocol' ) || 'lucien'
1524+
1525+ // Use the Lucien specified servers from the external JS file
1526+ const defaultSpecifiedServers = [ ]
1527+ if ( isLucien ) {
1528+ // Get servers based on whether we're in staging or production
1529+ defaultSpecifiedServers . push ( ...lucienSpecifiedServers ) ;
1530+ }
1531+
1532+ function serverSortRule ( serverA , serverB ) {
1533+
1534+ function determineServerType ( server ) {
1535+ if ( defaultSpecifiedServers . includes ( server . name ) ) {
1536+ return 'isPinned' ;
1537+ }
1538+ if ( server . is_official ) {
1539+ return 'isOfficial' ;
1540+ }
1541+ return 'others' ;
1542+ }
1543+
1544+ const serverAType = determineServerType ( serverA ) ;
1545+ const serverBType = determineServerType ( serverB ) ;
1546+ return sortRule [ serverAType ] - sortRule [ serverBType ] ;
1547+ }
14831548
14841549 // First load GitHub star counts, then fetch server data
14851550 loadGitHubStarCounts ( )
@@ -1493,6 +1558,8 @@ <h3>Raw Manifest:</h3>
14931558 . then ( data => {
14941559 serversMap = data ;
14951560 serversArray = Object . values ( serversMap ) ;
1561+ // sort servers by sortRule
1562+ serversArray . sort ( serverSortRule ) ;
14961563
14971564 // Generate tag filters dynamically from server data
14981565 generateTagFilters ( serversArray ) ;
@@ -2006,6 +2073,48 @@ <h3>Raw Manifest:</h3>
20062073 // Use the star count element created in the header
20072074
20082075 serverContent . appendChild ( meta ) ;
2076+
2077+ // Add install button with click function for deeplink
2078+ if ( isLucien ) {
2079+ const installButton = document . createElement ( 'button' ) ;
2080+ installButton . className = 'install-button' ;
2081+ installButton . type = 'button' ;
2082+
2083+ // Add download icon
2084+ installButton . innerHTML = `
2085+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2086+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
2087+ <polyline points="7 10 12 15 17 10"></polyline>
2088+ <line x1="12" y1="15" x2="12" y2="3"></line>
2089+ </svg>
2090+ Install
2091+ ` ;
2092+
2093+ // Handle click event to build search params and trigger deeplink
2094+ installButton . addEventListener ( 'click' , ( event ) => {
2095+ // Prevent the modal from opening
2096+ event . stopPropagation ( ) ;
2097+
2098+ // Build search params with server install info
2099+ const params = new URLSearchParams ( ) ;
2100+ params . append ( 'name' , server . name ) ;
2101+ params . append ( 'action' , 'install' ) ;
2102+
2103+ const installInfo = Object . values ( server . installations ) [ 0 ] ;
2104+ // No remote for now, only stdio
2105+ params . append ( 'command' , installInfo . command ) ;
2106+ params . append ( 'args' , encodeURIComponent ( installInfo . args . join ( ' ' ) ) ) ;
2107+ if ( installInfo . env ) {
2108+ params . append ( 'env' , JSON . stringify ( installInfo . env ) ) ;
2109+ }
2110+
2111+ // Create and trigger the deeplink
2112+ const deeplink = `${ sourceProtocol } ://mcpm?${ params . toString ( ) } ` ;
2113+ window . location . href = deeplink ;
2114+ } ) ;
2115+
2116+ serverContent . appendChild ( installButton ) ;
2117+ }
20092118
20102119 const linksContainer = document . createElement ( 'div' ) ;
20112120 linksContainer . className = 'server-links' ;
0 commit comments