11use serde:: Deserialize ;
2- use std:: env ;
2+ use std:: fs ;
33use zed:: settings:: ContextServerSettings ;
44use zed_extension_api:: { self as zed, serde_json, Command , ContextServerId , Project , Result } ;
55
6- const PACKAGE_NAME : & str = "@modelcontextprotocol/server- github" ;
7- const SERVER_PATH : & str = "node_modules/@modelcontextprotocol/server- github/dist/index.js " ;
6+ const REPO_NAME : & str = "github/ github-mcp-server " ;
7+ const BINARY_NAME : & str = "github-mcp-server " ;
88
99#[ derive( Debug , Deserialize ) ]
1010struct GitHubContextServerSettings {
1111 github_personal_access_token : String ,
1212}
1313
14- struct GitHubModelContextExtension ;
14+ struct GitHubModelContextExtension {
15+ cached_binary_path : Option < String > ,
16+ }
17+
18+ impl GitHubModelContextExtension {
19+ fn context_server_binary_path (
20+ & mut self ,
21+ _context_server_id : & ContextServerId ,
22+ ) -> Result < String > {
23+ if let Some ( path) = & self . cached_binary_path {
24+ if fs:: metadata ( path) . map_or ( false , |stat| stat. is_file ( ) ) {
25+ return Ok ( path. clone ( ) ) ;
26+ }
27+ }
28+
29+ let release = zed:: latest_github_release (
30+ REPO_NAME ,
31+ zed:: GithubReleaseOptions {
32+ require_assets : true ,
33+ pre_release : false ,
34+ } ,
35+ ) ?;
36+
37+ let ( platform, arch) = zed:: current_platform ( ) ;
38+ let asset_name = format ! (
39+ "{BINARY_NAME}_{os}_{arch}.{ext}" ,
40+ arch = match arch {
41+ zed:: Architecture :: Aarch64 => "arm64" ,
42+ zed:: Architecture :: X86 => "i386" ,
43+ zed:: Architecture :: X8664 => "x86_64" ,
44+ } ,
45+ os = match platform {
46+ zed:: Os :: Mac => "Darwin" ,
47+ zed:: Os :: Linux => "Linux" ,
48+ zed:: Os :: Windows => "Windows" ,
49+ } ,
50+ ext = match platform {
51+ zed:: Os :: Mac | zed:: Os :: Linux => "tar.gz" ,
52+ zed:: Os :: Windows => "zip" ,
53+ }
54+ ) ;
55+
56+ let asset = release
57+ . assets
58+ . iter ( )
59+ . find ( |asset| asset. name == asset_name)
60+ . ok_or_else ( || format ! ( "no asset found matching {:?}" , asset_name) ) ?;
61+
62+ let version_dir = format ! ( "{BINARY_NAME}-{}" , release. version) ;
63+ fs:: create_dir_all ( & version_dir)
64+ . map_err ( |err| format ! ( "failed to create directory '{version_dir}': {err}" ) ) ?;
65+ let binary_path = format ! ( "{version_dir}/{BINARY_NAME}" ) ;
66+
67+ if !fs:: metadata ( & binary_path) . map_or ( false , |stat| stat. is_file ( ) ) {
68+ let file_kind = match platform {
69+ zed:: Os :: Mac | zed:: Os :: Linux => zed:: DownloadedFileType :: GzipTar ,
70+ zed:: Os :: Windows => zed:: DownloadedFileType :: Zip ,
71+ } ;
72+
73+ zed:: download_file ( & asset. download_url , & version_dir, file_kind)
74+ . map_err ( |e| format ! ( "failed to download file: {e}" ) ) ?;
75+
76+ zed:: make_file_executable ( & binary_path) ?;
77+
78+ // Removes old versions
79+ let entries =
80+ fs:: read_dir ( "." ) . map_err ( |e| format ! ( "failed to list working directory {e}" ) ) ?;
81+ for entry in entries {
82+ let entry = entry. map_err ( |e| format ! ( "failed to load directory entry {e}" ) ) ?;
83+ if entry. file_name ( ) . to_str ( ) != Some ( & version_dir) {
84+ fs:: remove_dir_all ( entry. path ( ) ) . ok ( ) ;
85+ }
86+ }
87+ }
88+
89+ self . cached_binary_path = Some ( binary_path. clone ( ) ) ;
90+ Ok ( binary_path)
91+ }
92+ }
1593
1694impl zed:: Extension for GitHubModelContextExtension {
1795 fn new ( ) -> Self {
18- Self
96+ Self {
97+ cached_binary_path : None ,
98+ }
1999 }
20100
21101 fn context_server_command (
22102 & mut self ,
23- _context_server_id : & ContextServerId ,
103+ context_server_id : & ContextServerId ,
24104 project : & Project ,
25105 ) -> Result < Command > {
26- let latest_version = zed:: npm_package_latest_version ( PACKAGE_NAME ) ?;
27- let version = zed:: npm_package_installed_version ( PACKAGE_NAME ) ?;
28- if version. as_deref ( ) != Some ( latest_version. as_ref ( ) ) {
29- zed:: npm_install_package ( PACKAGE_NAME , & latest_version) ?;
30- }
31-
32106 let settings = ContextServerSettings :: for_project ( "mcp-server-github" , project) ?;
33107 let Some ( settings) = settings. settings else {
34108 return Err ( "missing `github_personal_access_token` setting" . into ( ) ) ;
@@ -37,12 +111,8 @@ impl zed::Extension for GitHubModelContextExtension {
37111 serde_json:: from_value ( settings) . map_err ( |e| e. to_string ( ) ) ?;
38112
39113 Ok ( Command {
40- command : zed:: node_binary_path ( ) ?,
41- args : vec ! [ env:: current_dir( )
42- . unwrap( )
43- . join( SERVER_PATH )
44- . to_string_lossy( )
45- . to_string( ) ] ,
114+ command : self . context_server_binary_path ( context_server_id) ?,
115+ args : vec ! [ "stdio" . to_string( ) ] ,
46116 env : vec ! [ (
47117 "GITHUB_PERSONAL_ACCESS_TOKEN" . into( ) ,
48118 settings. github_personal_access_token,
0 commit comments