Siento que estamos en un punto en el que el Protocolo de Contexto Modelo (MCP) se siente casi sinónimo de ingeniería GenAI. Anthropic introdujo MCP en noviembre de 2024, revolucionando la ingeniería GenAI. Nos llevó al punto donde, como ingenieros, podemos implementar varias herramientas basadas en nuestro caso de uso, y dirigir el LLM de nuestra elección para usar esas herramientas desde el confort de nuestros clientes de escritorio favoritos como Claude. Desde su aparición, han existido numerosos servidores MCP desarrollados por ingenieros entusiastas. Esto fue posible debido al rápido desarrollo de SDK de MCP en varios idiomas, incluyendo , de Y más recientemente, (YYY ) Yo he visto ¡Y yo sabía que tenía que tomarlo por un spin! Python Tipografía Golán Esta propuesta oficial para un Golang SDK ¿Qué es el MCP, se pregunta? Comencemos con una rápida introducción al MCP, que es corto para Hasta hace poco, la ingeniería de la IA requería una cuidadosa evaluación de las capacidades de varios LLM para seleccionar el correcto. Con MCP, puede seleccionar el LLM de su elección, y ampliar sus capacidades mediante la implementación de herramientas personalizadas y la conexión a fuentes de datos externas usted mismo! Las principales construcciones del protocolo son: Model Context Protocol Cliente de MCP Servidores MCP Capacidades (Herramientas, Recursos, Prompts) El LLM Un servidor MCP tiene ciertas capacidades determinadas por: Herramientas - Las funciones que puede ejecutar, como listar todos los correos electrónicos de un remitente, buscar un PR en Github Recursos - Fuente de datos externa, como una lista de documentos a los que desea que el LLM se refiera Prompts - Un conjunto de plantillas que ayudan a los usuarios a obtener las respuestas deseadas rápidamente Hay muchos servidores MCP ya disponibles que puedes empezar a utilizar para tus aplicaciones. https://github.com/punkpeye/awesome-mcp-servers BYOM (Traer su propio servidor MCP) En este post, quiero hablar de cómo podemos desarrollar nuestro propio servidor MCP utilizando Golang.Hace unos días, por cualquier razón, quería ir por todos los repositorios en el . Encuentros en GitHub Ahora podría haber usado el servidor MCP oficial de GitHub, pero aunque ofrece un gran conjunto de herramientas para repositorios y organizaciones, no tenía una herramienta directa para listar todos los repositorios en una organización. Desarrollar un servidor MCP en Golang Este es el SDK oficial de Golang MCP: El README y ejemplos / carpetas contienen excelentes ejemplos sobre el desarrollo de un servidor y cliente MCP. https://github.com/modelcontextprotocol/go-sdk Siguiendo estos ejemplos, decidí crear un servidor MCP de la siguiente manera: server := mcp.NewServer(&mcp.Implementation{ Name: "demo-github-mcp", Title: "A demo github mcp server", Version: "0.0.1", }, nil) El siguiente paso fue proporcionar al servidor la capacidad de listar todos los repositorios en un org de GitHub. type ToolHandlerFor[In, Out any] func(context.Context, *ServerSession, *CallToolParamsFor[In]) (*CallToolResultFor[Out], error) Después de esto, creamos un herramienta, que acepta los siguientes argumentos relacionados con el Github org como entrada: 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: Verificación de entrada para garantizar que solo aceptamos argumentos válidos if params == nil { return nil, fmt.Errorf("empty params") } args := params.Arguments if args.Name == "" && args.URL == "" { return nil, fmt.Errorf("empty args") } Formar la URL de la API de GitHub a partir de la entrada basada en los documentos de la API de 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" Envía tu solicitud y recibe una respuesta 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)) } Analizar la respuesta de la API de 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) } Devuelve la respuesta como un contexto textual 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 Después de definir la herramienta, el siguiente paso es registrarla con el servidor 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) A continuación, es hora de iniciar el servidor! Para este servidor MCP demo, estoy utilizando el transporte de estadio, que permite al servidor comunicarse a través de STDIN y STDOUT. Este es el enfoque estándar para las integraciones locales de MCP con clientes como 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...") Así es como se ve el código con todo reunido: 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 } Ahora el último paso es compilar esto y generar el ejecutable con: go build Uso del servidor MCP con un cliente como Claude Veamos cómo agregar este servidor al escritorio de Claude. Abra la aplicación de escritorio Claude y vaya a Claude -> Configuración -> Desarrollador. Aquí verá una sección llamada Local MCP Servers. Haga clic en el botón de abajo llamado Edit Config. Esto abrirá el archivo Claude config. Editar el archivo para agregar el servidor MCP de la siguiente manera: { "mcpServers": { "demo-github-mcp": { "command": "/path/to/executable/generated/from/go build", "args": [] } } } Reiniciar la aplicación Claude (Actualmente, esta es la única manera de actualizar los servidores MCP dentro de Claude) ¡Y ahora es el momento de probar esto!Este es el prompt que di: List all repositories in the Kubernetes github org Claude reconoció el servidor demo-github-mcp que funcionaba localmente y preguntó si podía usarlo. Tan pronto como lo aprobé, Claude enumeró los repositorios en los org e incluso mostró la herramienta utilizada (list-repositories) al inicio de la respuesta: ¡Vimos cómo podemos desarrollar un servidor MCP simple utilizando Golang, y usarlo a través de clientes MCP como Claude! El “momento” Mientras estaba desarrollando este servidor, continué preguntándome: “Si estoy escribiendo el código para listar repositorios, ¿qué va a hacer el LLM?”, y luego de repente se hizo clic - el LLM permite la comunicación a este servidor a través del lenguaje natural! Cuando Claude Sonnet 4 leyó mi prompt - "Lista de todos los repositorios en el Kubernetes GitHub org" - y por sí mismo reconoció que el servidor MCP operado localmente funcionaría mejor para el prompt, mi mente se derrumbó 😀 En ese momento, no tuve que proporcionarle ninguna información sobre el nombre de la herramienta o los parámetros que aceptaba. Consideraciones de producción Mientras que nuestro servidor MCP funciona muy bien para fines de demostración, hay varias características importantes que faltan para el uso de la producción: Gestión de páginas: la API de GitHub devuelve resultados paginados, por defecto 30 entradas por página. En el código anterior he aumentado a 100 con el parámetro de consulta por página. Para el código de producción, procesarías el encabezado de respuesta Link para obtener información sobre la siguiente página y el número total de páginas. (Referir a esto) Limitación de tarifas: el código anterior no tiene en cuenta los límites de tarifas de la API de GitHub Autenticación: Las solicitudes autenticadas tienen un límite de tasa más alto que las solicitudes no autenticadas. ¿Qué es lo siguiente? Esta demostración del servidor MCP de GitHub fue un buen comienzo. Pero la principal razón por la que estoy escribiendo esto es para demostrar cuán fácil puede ser personalizar su herramienta GenAI favorita a su gusto! También es sorprendentemente sencillo comenzar con un servidor MCP en Golang para sus propios proyectos! ¡No puedo esperar a ver los poderosos servicios creados a partir de la combinación de la flexibilidad de MCP y la naturaleza de alto rendimiento de Golang!