← Πίσω στο Blog

Δικός σας MCP server για το Claude Code σε Go

mcp claude-code go εργαλεία-ανάπτυξης bbolt mcp-go
Δικός σας MCP server για το Claude Code σε Go

Ένας δικός σας MCP server μετατρέπει τη θολή, ακριβή σε tokens δουλειά ενός LLM — συναθροίσεις, υπολογισμούς και συνεπείς εγγραφές στη βάση — σε μία ντετερμινιστική κλήση εργαλείου. Αντί να ζητάτε από το μοντέλο να τρέχει εντολές, να διαβάζει κείμενο και να μαντεύει, του δίνετε ένα τυποποιημένο εργαλείο με περιγεγραμμένο σχήμα και ένα επικυρωμένο αποτέλεσμα. Και μπορείτε να τον φτιάξετε ως ένα μόνο Go binary μέσα σε ένα απόγευμα.

Αυτός είναι ένας οδηγός. Αυτό ακριβώς το μοτίβο το παραδίδουμε στο binzaar, το ανοιχτού κώδικα σκελετό μας για micro-apps: ένα Go binary που είναι ταυτόχρονα ένας MCP server πάνω από stdio, μια διεπαφή τερματικού και μια ενσωματωμένη βάση πάνω από ένα ενιαίο στρώμα use-cases. Παρακάτω φτιάχνετε τον δικό σας, βήμα βήμα, με τα πραγματικά API mark3labs/mcp-go και go.etcd.io/bbolt που χρησιμοποιούμε.

Γιατί ένας MCP server υπερτερεί ενός εργαλείου CLI ή του σκέτου prompting

Ένα εργαλείο MCP δίνει στο μοντέλο ένα τυποποιημένο σχήμα, επικυρωμένες εισόδους και ένα δομημένο αποτέλεσμα. Ένα εργαλείο CLI του δίνει ένα παιχνίδι εικασιών. Όταν το Claude Code χειρίζεται ένα CLI, πρέπει να θυμάται τα flags, να τρέχει το binary, να αναλύει το ελεύθερο κείμενο του stdout και να ελπίζει ότι η ανάλυση κράτησε. Καθένα από αυτά τα βήματα κοστίζει tokens και μπορεί να αποτύχει σιωπηλά.

Με το MCP, το μοντέλο καλεί μία περιγεγραμμένη συνάρτηση και παίρνει πίσω JSON. Λιγότερα tokens, λιγότερες μεταβάσεις και μια έξοδος που το μοντέλο δεν μπορεί να θαλασσώσει.

flowchart TB
    subgraph CLI["Εργαλείο CLI — το μοντέλο τρέχει εντολές"]
      direction TB
      M1["Claude Code"] --> G1["μαντεύει τα flags"] --> R1["τρέχει το binary"] --> P1["αναλύει το κείμενο του stdout"] --> X1["ελπίζει ότι ταίριαξε"]:::bad
    end
    subgraph MCP["Εργαλείο MCP — μία τυποποιημένη κλήση"]
      direction TB
      M2["Claude Code"] --> C2["κλήση add_lead(name, email)"] --> V2["το σχήμα επικυρώνει την είσοδο"] --> S2["δομημένο αποτέλεσμα JSON"]:::good
    end
    classDef good fill:#494fdf,stroke:#376cd5,color:#ffffff,stroke-width:2px
    classDef bad fill:#1e293b,stroke:#64748b,color:#f1f5f9,stroke-width:1.5px

Υπάρχει και δεύτερος λόγος: το .mcp.json είναι ένα κοινό πρότυπο. Το Claude Code το διαβάζει, όπως και άλλοι MCP clients. Ένας server που γράφετε εσείς εξυπηρετεί κάθε εργαλείο που μιλάει το πρωτόκολλο — δεν φτιάχνετε μια ενσωμάτωση μόνο για το Claude.

Τι λύνει στην πράξη ένας MCP server

Το βασικό κέρδος είναι η αποδοτικότητα και η συνέπεια. Κάποια δουλειά είναι ακριβή για ένα LLM μέσω prompt και φθηνή για ένα μικρό binary να τη βγάλει σωστά. Μεταφέρετε αυτή τη δουλειά σε ένα εργαλείο.

Τρεις περιπτώσεις αποδίδουν αμέσως:

  • Συναθροίσεις και υπολογισμοί. Το να αθροίζετε, να ομαδοποιείτε ή να ταξινομείτε εκατοντάδες γραμμές μέσα στο prompt καίει tokens και προσκαλεί αριθμητικά λάθη. Ένα εργαλείο report_totals επιστρέφει τον αριθμό.
  • Εγγραφές στη βάση με συνέπεια από το μοντέλο. Αυτό είναι το μεγάλο. Αν αφήσετε το μοντέλο να γράφει γραμμές στο χέρι, παρεκκλίνει — εδώ ένα πεδίο μετονομασμένο, εκεί ένας τύπος στραμπουληγμένος. Δώστε του ένα μόνο εργαλείο create_lead ή add_install που επιβάλλει το σχήμα και την επικύρωση, και κάθε εγγραφή είναι ίδια.
  • Οριοθετημένες, αναζητήσιμες επιφάνειες. Αντί να ρίχνετε έναν ολόκληρο πίνακα στο context, ένα εργαλείο search παίρνει μια αναζήτηση και επιστρέφει μία σελίδα. Το μοντέλο βλέπει μόνο ό,τι ζήτησε.

Ο ντετερμινισμός είναι το ζητούμενο. Η ίδια κλήση με την ίδια είσοδο δίνει το ίδιο αποτέλεσμα, και το μοντέλο δεν μπορεί να εφεύρει μια μορφή που δεν ορίσατε.

Στήστε το έργο Go

Ξεκινήστε από ένα πρότυπο, ώστε να πετύχετε τη σωστή στρωμάτωση από την πρώτη μέρα. Το binzaar init αφήνει ένα ενσωματωμένο kit για το Claude Code στον κατάλογό σας: ένα .claude/ με τους κανόνες που μετράνε (mcp-server.md, db-rules.md) και μια ροή εργασίας οδηγούμενη από προδιαγραφή — το /product-idea γράφει την προδιαγραφή, το /app-init στήνει το module Go, το /app-spec-sync κρατά κώδικα και προδιαγραφή σε βήμα.

Η μορφή είναι μία περιοχή πίσω από τρία πρόσωπα, όλα στηριγμένα στο ίδιο ένα αρχείο:

flowchart TB
    DB["ενσωματωμένη βάση bbolt<br/>(ένα αρχείο, χωρίς server)"]:::core
    UC["στρώμα use-cases<br/>(τα ρήματά σας)"]:::core
    DB --> UC
    UC --> MCP["MCP server πάνω από stdio<br/>(Claude Code, άλλοι clients)"]:::face
    UC --> TUI["διεπαφή τερματικού<br/>(άνθρωποι)"]:::face
    classDef core fill:#494fdf,stroke:#376cd5,color:#ffffff,stroke-width:2px
    classDef face fill:#1e293b,stroke:#64748b,color:#f1f5f9,stroke-width:1.5px

Δύο βιβλιοθήκες σηκώνουν το βάρος:

  • MCP: github.com/mark3labs/mcp-go — τύποι του πρωτοκόλλου και το αντικείμενο του server.
  • Αποθήκευση: go.etcd.io/bbolt — ένας ενσωματωμένος, ACID key/value store σε ένα αρχείο. Καθαρή Go, χωρίς server, χωρίς cgo. Δώστε του alias bolt και μην καταφύγετε ποτέ στο παρωχημένο github.com/boltdb/bolt.

Το αποτέλεσμα είναι ένα binary με μια κατάσταση servemcp) που τρέχει τον stdio server, συν μια προεπιλεγμένη κατάσταση για την TUI. Το main μένει λιτό· το cmd/ αναλύει την κατάσταση και ανοίγει το ένα αρχείο της βάσης.

Περιγράψτε την ανάγκη σας ως εργαλεία

Ονομάστε τα use-cases σας ως ρήματα και μετατρέψτε κάθε ρήμα σε εργαλείο. Κάντε πρώτα μία ερώτηση: τι πρέπει να κάνει το μοντέλο που είναι ακριβό να γίνει μέσω prompt; Κάθε απάντηση είναι ένα εργαλείο με περιγεγραμμένο σχήμα.

Βάζετε πάντα μια περιγραφή στο εργαλείο και σε κάθε παράμετρο — το μοντέλο στηρίζεται σε αυτά τα κείμενα για να ξέρει πότε και πώς να το καλέσει. Ορίστε ένα πραγματικό εργαλείο add_lead με τη σύμβαση σφαλμάτων που ακολουθούμε:

package server
import (
"context"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
// New builds a transport-agnostic server; cmd/ picks the transport.
func New(name, version string, h *handler) *server.MCPServer {
s := server.NewMCPServer(name, version,
server.WithToolCapabilities(true),
server.WithRecovery(), // a panicking handler can't crash the process
)
s.AddTool(mcp.NewTool("add_lead",
mcp.WithDescription("Create one lead in the database with validated fields."),
mcp.WithString("name", mcp.Required(), mcp.Description("Full name of the lead")),
mcp.WithString("email", mcp.Required(), mcp.Description("Contact email")),
mcp.WithString("source",
mcp.Enum("web", "referral", "event"),
mcp.Description("Where the lead came from")),
), h.addLead)
return s
}
func (h *handler) addLead(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
name, err := req.RequireString("name")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil // user error → result, nil
}
email, err := req.RequireString("email")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
lead, err := h.app.CreateLead(ctx, name, email, req.GetString("source", "web"))
if err != nil {
return nil, err // infrastructure error → nil, err
}
return mcp.NewToolResultJSON(lead)
}

Αυτός ο διαχωρισμός σφαλμάτων έχει σημασία. Μια κακή είσοδος ή ένας παραβιασμένος επιχειρησιακός κανόνας επιστρέφει NewToolResultError(...), nil — ένα αποτέλεσμα που το μοντέλο μπορεί να διαβάσει και να διορθώσει. Ένα σφάλμα που το μοντέλο δεν μπορεί να διορθώσει (η βάση έπεσε, ο δίσκος γέμισε) επιστρέφει nil, err ως πραγματικό σφάλμα πρωτοκόλλου. Αν τα μπερδέψετε, είτε κρύβετε πραγματικά σφάλματα είτε οδηγείτε το μοντέλο σε αδιέξοδο για ένα λάθος που θα μπορούσε να αναιρέσει.

Ρυθμίστε την επιφάνεια των εργαλείων

Μόλις ένα εργαλείο παίρνει περισσότερα από ένα ή δύο ορίσματα, σταματήστε να διαβάζετε ωμές παραμέτρους και περάστε σε έναν τυποποιημένο handler. Ορίστε ένα struct εισόδου με tags jsonschema, καταχωρίστε το με mcp.WithInputSchema[T]() και τυλίξτε τον handler με mcp.NewStructuredToolHandler(fn) — η είσοδος επικυρώνεται και δένεται πριν τρέξει ο κώδικάς σας.

type SearchRequest struct {
Query string `json:"query" jsonschema:"required,description=Search text"`
Limit int `json:"limit" jsonschema:"description=Max results, default 20"`
}

Τρεις κανόνες κρατούν έναν server τίμιο κάτω από έναν agent:

  • Σελιδοποιήστε τα εργαλεία λίστας — μη ρίχνετε ποτέ τα πάντα. Δώστε σε κάθε εργαλείο λίστας μια αναζήτηση, μια σειρά ταξινόμησης και ένα οριοθετημένο μέγεθος σελίδας. Περιορίστε ένα υπερβολικό limit στο μέγιστο αντί να το απορρίπτετε· το μοντέλο δεν πρέπει να χρειάζεται να ξαναπροσπαθήσει για να μάθει το ανώτατο όριό σας.
  • Καταγράφετε στο stderr, ποτέ στο stdout. Στο stdio, το stdout είναι το κανάλι του πρωτοκόλλου. Ένα ξεστρατισμένο fmt.Println καταστρέφει το stream. Γράφετε κάθε γραμμή log στο stderr.
  • Κρατήστε την κατασκευή ανεξάρτητη από τη μεταφορά. Το internal/server χτίζει και καταχωρίζει εργαλεία χωρίς καμία αναφορά σε μεταφορά. Το cmd/ επιλέγει stdio:
// cmd/ selects the transport; internal/server stays transport-agnostic.
if err := server.ServeStdio(s); err != nil {
fmt.Fprintln(os.Stderr, "mcp server:", err) // stderr, not stdout
os.Exit(1)
}

Δηλώστε τον στο .mcp.json

Το Claude Code εντοπίζει τον server σας από ένα .mcp.json τοπικό στο έργο. Περιέχει έναν χάρτη mcpServers· κάθε εγγραφή είναι μια εκκίνηση stdio — ένα command και τα args του — που δείχνει στο χτισμένο binary σας στην κατάσταση serve:

{
"mcpServers": {
"leaddesk": {
"command": "/home/you/.local/share/binzaar/bin/leaddesk",
"args": ["serve"]
}
}
}

Βάλτε αυτό το αρχείο στη ρίζα του έργου, επανεκκινήστε το Claude Code, και τα εργαλεία σας εμφανίζονται. Επειδή το αρχείο είναι κοινό πρότυπο, κάθε άλλος MCP client στο έργο διαβάζει την ίδια εγγραφή. Βάλτε το command να δείχνει σε μια απόλυτη διαδρομή προς το binary· το όρισμα serve είναι αυτό που το γυρίζει από την κατάσταση TUI στον stdio server.

Προαιρετικά: αφήστε το binzaar να το κάνει για εσάς

Αν προτιμάτε να μην το κάνετε με το χέρι, το binzaar μπορεί. Στήνει μια ολοκαίνουργια micro-app από ένα πρότυπο και την παραδίδει στη ροή προδιαγραφής /product-idea, και το βήμα του configure_mcp γράφει για μια εγκατεστημένη app ακριβώς την εγγραφή .mcp.json παραπάνω — προσθέτει ή αντικαθιστά αυτό το ένα κλειδί server και αφήνει κάθε άλλο κλειδί στο αρχείο ανέγγιχτο. Ο κώδικας και οι κανόνες είναι ανοιχτού κώδικα στο github.com/Techthos/binzaar.

Το συμπέρασμα ισχύει έτσι κι αλλιώς: ένας δικός σας MCP server μετατρέπει το ακριβό, επιρρεπές σε παρέκκλιση prompting σε μία ντετερμινιστική κλήση εργαλείου, και ένα μόνο Go binary με ενσωματωμένη βάση αρκεί για να το παραδώσετε. Ξεκινήστε από το ένα ρήμα που σας κοστίζει τα περισσότερα tokens σήμερα, και δώστε στο μοντέλο ένα εργαλείο για αυτό.

Αν θέλετε ένα χέρι βοηθείας στον σχεδιασμό της επιφάνειας εργαλείων για το δικό σας stack, επικοινωνήστε μαζί μας.