Mi sembra che stiamo arrivando a un punto in cui il Modello Context Protocol (MCP) si sente quasi sinonimo di ingegneria GenAI. Anthropic ha introdotto MCP nel novembre 2024, rivoluzionando l'ingegneria GenAI. Ci ha portato al punto in cui, come ingegneri, possiamo implementare vari strumenti in base al nostro caso di utilizzo, e indirizzare il LLM della nostra scelta per utilizzare questi strumenti dal comfort dei nostri preferiti IDEs o client desktop come Claude. Da quando è arrivato, ci sono stati numerosi server MCP sviluppati da ingegneri entusiasti. Questo è stato possibile a causa del rapido sviluppo di SDK MCP in diverse lingue, tra cui di E più di recente, Io ho visto, io ho visto E sapevo che dovevo prenderlo per uno spin! di Python Tipi di script Il Golang Questa proposta ufficiale per un Golang SDK Che cos’è il MCP, vi domandate? Iniziamo con una rapida introduzione al MCP, che è breve per Fino a poco tempo fa, l'ingegneria AI richiedeva una valutazione accurata delle capacità dei vari LLM per selezionare quello giusto. Con MCP, è possibile selezionare il LLM di vostra scelta e estendere le sue capacità implementando strumenti personalizzati e connettersi alle fonti di dati esterne da soli! Le principali strutture del protocollo sono: Model Context Protocol Cliente MCP Servizio MCP Capacità (Strumenti, Risorse, Prompts) Il LLM Un server MCP ha alcune capacità determinate da: Strumenti - Le funzioni che può eseguire, come l'elenco di tutte le e-mail di un mittente, la ricerca di un PR su Github Risorse - Fonte dati esterna, come un elenco di documenti a cui si desidera che il LLM si riferisca Prompts - Un set di modelli che aiutano gli utenti a ottenere le risposte desiderate rapidamente Ci sono molti server MCP già disponibili che puoi iniziare a utilizzare per le tue applicazioni. Puoi fare riferimento a questa compilazione di incredibili server MCP: https://github.com/punkpeye/awesome-mcp-servers BYOM (portare il proprio server MCP) In questo post, voglio andare oltre come possiamo sviluppare il nostro proprio server MCP utilizzando Golang. Qualche giorno fa, per qualsiasi ragione,n volevo andare sopra tutti i repositori nel . Github di Kubernetes Ora avrei potuto usare il server MCP ufficiale di GitHub, ma anche se offre un ottimo set di strumenti per repositori e organizzazioni, non ha uno strumento diretto per elencare tutti i repositori in un'organizzazione. Sviluppo di un server MCP a Golang Questo è il Golang MCP SDK ufficiale: Il README e gli esempi / cartelle contengono ottimi esempi di sviluppo di un server e client MCP. https://github.com/modelcontextprotocol/go-sdk Seguendo questi esempi, ho deciso di creare un server MCP come segue: server := mcp.NewServer(&mcp.Implementation{ Name: "demo-github-mcp", Title: "A demo github mcp server", Version: "0.0.1", }, nil) Il passo successivo era quello di fornire al server la possibilità di elencare tutti i repositori in un GitHub org. Questo può essere fatto implementando uno strumento di tipo ToolHandler come fornito dal SDK. type ToolHandlerFor[In, Out any] func(context.Context, *ServerSession, *CallToolParamsFor[In]) (*CallToolResultFor[Out], error) In seguito, ho creato un strumento, che accetta come input i seguenti argomenti relativi al Github org: ListRepositories // User can pass in either the name of the org (example: kubernetes), or its URL (example: https://github.com/kubernetes) type GithubOrgArgs struct { Name string URL string } func ListRepositories(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolParamsFor[GithubOrgArgs]) (*mcp.CallToolResultFor[struct{}], error) { Now let's go over the body of ListRepositories step-by-step: Verifica di input per garantire che accettiamo solo argomenti validi if params == nil { return nil, fmt.Errorf("empty params") } args := params.Arguments if args.Name == "" && args.URL == "" { return nil, fmt.Errorf("empty args") } Formare l'URL dell'API di GitHub dall'ingresso basato sui documenti API di GitHub var apiURL string var organization string if args.URL != "" { // If URL is provided, extract org name and build API URL url := strings.TrimPrefix(args.URL, "https://") url = strings.TrimPrefix(url, "http://") url = strings.TrimPrefix(url, "github.com/") url = strings.TrimSuffix(url, "/") orgName := strings.Split(url, "/")[0] apiURL = fmt.Sprintf("https://api.github.com/orgs/%s/repos", orgName) organization = orgName } else { // Use the provided organization name apiURL = fmt.Sprintf("https://api.github.com/orgs/%s/repos", args.Name) organization = args.Name } apiURL = apiURL + "?per_page=100" Invia la richiesta e ricevi una risposta client := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) if err != nil { return nil, err } req.Header.Add("Accept", "application/vnd.github.v3+json") resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("GitHub API error (status %d): %s", resp.StatusCode, string(body)) } Scopri le risposte dell'API GitHub type repository struct { Name string `json:"name"` FullName string `json:"full_name"` HTMLURL string `json:"html_url"` Private bool `json:"private"` } // Parse the JSON response var repositories []repository if err := json.NewDecoder(resp.Body).Decode(&repositories); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } Ritorna la risposta come contesto var result strings.Builder result.WriteString(fmt.Sprintf("Repositories for organization %s:", organization)) for _, repo := range repositories { result.WriteString(fmt.Sprintf("Name: %s, URL: %s", repo.Name, repo.HTMLURL)) } return &mcp.CallToolResultFor[struct{}]{ Content: []mcp.Content{ &mcp.TextContent{Text: result.String()}, }, }, nil Dopo aver definito lo strumento, il passo successivo è quello di registrarlo con il server MCP: mcp.AddTool(server, &mcp.Tool{ Name: "list-repositories", Description: "A tool to list all repositories in a Github org", InputSchema: &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ "name": { Type: "string", Description: "GitHub organization name (e.g., kubernetes)", }, "url": { Type: "string", Description: "GitHub organization URL (e.g., https://github.com/kubernetes)", }, }, }, }, ListRepositories) Successivamente, è il momento di avviare il server! Per questo server MCP demo, sto usando il trasporto dello studio, che consente al server di comunicare tramite STDIN e STDOUT. Questo è l'approccio standard per le integrazioni MCP locali con client come Claude Desktop o VSCode. t := mcp.NewLoggingTransport(mcp.NewStdioTransport(), os.Stderr) log.Println("🚀 MCP server starting up...") if err := server.Run(context.Background(), t); err != nil { log.Printf("Server failed: %v", err) } log.Println("🚀 MCP server shutting down...") Ecco come appare il codice con tutto messo insieme: func main() { server := mcp.NewServer(&mcp.Implementation{ Name: "demo-github-mcp", Title: "A demo github mcp server", Version: "0.0.1", }, nil) mcp.AddTool(server, &mcp.Tool{ Name: "list-repositories", Description: "A tool to list all repositories in a Github org", InputSchema: &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ "name": { Type: "string", Description: "GitHub organization name (e.g., kubernetes)", }, "url": { Type: "string", Description: "GitHub organization URL (e.g., https://github.com/kubernetes)", }, }, }, }, ListRepositories) t := mcp.NewLoggingTransport(mcp.NewStdioTransport(), os.Stderr) log.Println("🚀 MCP server starting up...") if err := server.Run(context.Background(), t); err != nil { log.Printf("Server failed: %v", err) } log.Println("🚀 MCP server shutting down...") } type GithubOrgArgs struct { Name string URL string } func ListRepositories(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolParamsFor[GithubOrgArgs]) (*mcp.CallToolResultFor[struct{}], error) { if params == nil { return nil, fmt.Errorf("empty params") } args := params.Arguments if args.Name == "" && args.URL == "" { return nil, fmt.Errorf("empty args") } var apiURL string var organization string if args.URL != "" { // If URL is provided, extract org name and build API URL url := strings.TrimPrefix(args.URL, "https://") url = strings.TrimPrefix(url, "http://") url = strings.TrimPrefix(url, "github.com/") url = strings.TrimSuffix(url, "/") orgName := strings.Split(url, "/")[0] apiURL = fmt.Sprintf("https://api.github.com/orgs/%s/repos", orgName) organization = orgName } else { // Use the provided organization name apiURL = fmt.Sprintf("https://api.github.com/orgs/%s/repos", args.Name) organization = args.Name } apiURL = apiURL + "?per_page=100" client := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) if err != nil { return nil, err } req.Header.Add("Accept", "application/vnd.github.v3+json") resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("GitHub API error (status %d): %s", resp.StatusCode, string(body)) } type repository struct { Name string `json:"name"` FullName string `json:"full_name"` HTMLURL string `json:"html_url"` Private bool `json:"private"` } // Parse the JSON response var repositories []repository if err := json.NewDecoder(resp.Body).Decode(&repositories); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } var result strings.Builder result.WriteString(fmt.Sprintf("Repositories for organization %s:", organization)) for _, repo := range repositories { result.WriteString(fmt.Sprintf("Name: %s, URL: %s", repo.Name, repo.HTMLURL)) } return &mcp.CallToolResultFor[struct{}]{ Content: []mcp.Content{ &mcp.TextContent{Text: result.String()}, }, }, nil } Ora l'ultimo passo è quello di compilare questo e generare l'eseguibile con: go build Utilizzo del server MCP con un client come Claude Vediamo come aggiungere questo server al desktop di Claude. Apri l'app desktop Claude e vai a Claude -> Impostazioni -> Sviluppatore. Qui vedrai una sezione chiamata Local MCP Servers. Fai clic sul pulsante qui sotto chiamato Edit Config. Questo aprirà il file Claude config. Modifica il file per aggiungere il server MCP come segue: { "mcpServers": { "demo-github-mcp": { "command": "/path/to/executable/generated/from/go build", "args": [] } } } Riavviare l'app Claude (Attualmente, questo è l'unico modo per aggiornare i server MCP all'interno di Claude) E ora è il momento di testare questo!Questo è il prompt che ho dato: List all repositories in the Kubernetes github org Claude ha riconosciuto il server demo-github-mcp in esecuzione localmente e ha chiesto se poteva usarlo! Non appena l'ho approvato, Claude ha elencato i repositori nell'org e ha persino visualizzato lo strumento utilizzato (list-repositories) all'inizio della risposta: Abbiamo visto come possiamo sviluppare un semplice server MCP utilizzando Golang, e utilizzarlo attraverso i client MCP come Claude! Il “momento” Mentre stavo sviluppando questo server, ho continuato a chiedermi: “Se sto scrivendo il codice per elencare i repositori, che cosa farà il LLM?”, e poi improvvisamente ha cliccato - il LLM consente la comunicazione a questo server tramite il linguaggio naturale! Quando Claude Sonnet 4 ha letto il mio prompt - "Lista tutti i repositori nel Kubernetes GitHub org" - e da solo ha riconosciuto che il server MCP in esecuzione localmente funzionerebbe meglio per il prompt, la mia mente è stata spazzata via 😀. A quel punto, non ho dovuto fornire alcuna informazione sul nome dello strumento o i parametri che accetta. Ho chiesto la mia domanda attraverso una semplice frase, e ha capito quale strumento usare, come chiamare lo strumento, e ha fatto il lavoro! Considerazioni di produzione Mentre il nostro server MCP funziona bene per scopi demo, ci sono diverse caratteristiche importanti che mancano per l'uso di produzione: Gestione paginazione: l'API di GitHub restituisce risultati paginati, il default è di 30 voci per pagina. Nel codice di cui sopra ho aumentato a 100 con il parametro di query per_page. Per il codice di produzione, si tratterebbe l'intestazione di risposta Link per ottenere informazioni sulla pagina successiva e il numero totale di pagine. (Riferimento a questo) Limitazione delle tariffe: il codice di cui sopra non tiene conto dei limiti delle tariffe API di GitHub Autenticazione: le richieste autenticate hanno un limite di tasso più alto rispetto alle richieste non autenticate. Cos’è il prossimo? Ma il motivo principale per cui sto scrivendo questo è quello di dimostrare quanto sia facile personalizzare il tuo strumento GenAI preferito a tuo piacimento! È anche sorprendentemente semplice iniziare con un server MCP a Golang per i tuoi progetti! Non vedo l'ora di vedere i servizi potenti creati dalla combinazione della flessibilità di MCP e della natura ad alte prestazioni di Golang!