# Directory Structure ``` ├── .gitignore ├── client │ └── client.go ├── go.mod ├── go.sum ├── main.go ├── Makefile ├── mcp.go ├── README.md └── weaviate.go ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` */mcp-server */.idea/* ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # Weaviate MCP Server ## Instructions Build the server: ``` make build ``` Run the test client ``` make run-client ``` ## Tools ### Insert One Insert an object into weaviate. **Request body:** ```json {} ``` **Response body** ```json {} ``` ### Query Retrieve objects from weaviate with hybrid search. **Request body:** ```json {} ``` **Response body** ```json {} ``` ``` -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- ```go package main import ( "log" ) func main() { // TODO: get all WeaviateConn config from env // TODO: support SSEs // var transport string // flag.StringVar(&transport, "transport", "stdio", "Specifies the transport protocol. One of [stdio|sse]") s, err := NewMCPServer() if err != nil { log.Fatalf("failed to start mcp server: %v", err) } _ = s s.Serve() } ``` -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- ```go package main import ( "context" "fmt" "log" "github.com/mark3labs/mcp-go/client" "github.com/mark3labs/mcp-go/mcp" ) func main() { ctx := context.Background() cmd := "./mcp-server" c, err := newMCPClient(ctx, cmd) if err != nil { log.Fatal(err) } defer c.Close() { insertRes, err := insertRequest(ctx, c) if err != nil { log.Fatal(err) } log.Printf("insert-one response: %+v", insertRes) } { queryRes, err := queryRequest(ctx, c) if err != nil { log.Fatal(err) } log.Printf("query response: %+v", queryRes) } } func newMCPClient(ctx context.Context, cmd string) (*client.Client, error) { c, _ := client.NewStdioMCPClient(cmd, nil) initRes, err := c.Initialize(ctx, mcp.InitializeRequest{}) if err != nil { return nil, fmt.Errorf("failed to init client: %w", err) } log.Printf("init result: %+v", initRes) if err := c.Start(ctx); err != nil { return nil, fmt.Errorf("failed to start client: %w", err) } if err := c.Ping(ctx); err != nil { return nil, fmt.Errorf("failed to ping server: %w", err) } return c, nil } func insertRequest(ctx context.Context, c *client.Client) (*mcp.CallToolResult, error) { request := mcp.CallToolRequest{} request.Params.Name = "weaviate-insert-one" request.Params.Arguments = map[string]interface{}{ "collection": "WorldMap", "properties": map[string]interface{}{ "continent": "Europe", "country": "Spain", "city": "Valencia", }, } log.Printf("insert request: %+v", request) res, err := c.CallTool(ctx, request) if err != nil { return nil, fmt.Errorf("failed to call insert-one tool: %v", err) } return res, nil } func queryRequest(ctx context.Context, c *client.Client) (*mcp.CallToolResult, error) { request := mcp.CallToolRequest{} request.Params.Name = "weaviate-query" request.Params.Arguments = map[string]interface{}{ "collection": "WorldMap", "query": "What country is Valencia in?", "targetProperties": []string{"continent", "country", "city"}, } log.Printf("query request: %+v", request) res, err := c.CallTool(ctx, request) if err != nil { return nil, fmt.Errorf("failed to call query tool: %v", err) } return res, nil } ``` -------------------------------------------------------------------------------- /weaviate.go: -------------------------------------------------------------------------------- ```go package main import ( "context" "encoding/json" "errors" "fmt" "time" "github.com/weaviate/weaviate-go-client/v4/weaviate" "github.com/weaviate/weaviate-go-client/v4/weaviate/graphql" "github.com/weaviate/weaviate/entities/models" ) type WeaviateConnection struct { client *weaviate.Client } func NewWeaviateConnection() (*WeaviateConnection, error) { // TODO: get config from env client, err := weaviate.NewClient(weaviate.Config{ Host: "localhost:8080", Scheme: "http", StartupTimeout: time.Second, }) if err != nil { return nil, fmt.Errorf("connect to weaviate: %w", err) } return &WeaviateConnection{client}, nil } func (conn *WeaviateConnection) InsertOne(ctx context.Context, collection string, props interface{}, ) (*models.Object, error) { obj := models.Object{ Class: collection, Properties: props, } // Use batch to leverage autoschema and gRPC resp, err := conn.batchInsert(ctx, &obj) if err != nil { return nil, fmt.Errorf("insert one object: %w", err) } return &resp[0].Object, err } func (conn *WeaviateConnection) Query(ctx context.Context, collection, query string, targetProps []string, ) (string, error) { hybrid := graphql.HybridArgumentBuilder{} hybrid.WithQuery(query) res, err := conn.client.GraphQL().Get(). WithClassName(collection).WithHybrid(&hybrid). WithFields(func() []graphql.Field { fields := make([]graphql.Field, len(targetProps)) for i, prop := range targetProps { fields[i] = graphql.Field{Name: prop} } return fields }()...). Do(context.Background()) if err != nil { return "", err } b, err := json.Marshal(res) if err != nil { return "", fmt.Errorf("unmarshal query response: %w", err) } return string(b), nil } func (conn *WeaviateConnection) batchInsert(ctx context.Context, objs ...*models.Object) ([]models.ObjectsGetResponse, error) { resp, err := conn.client.Batch().ObjectsBatcher().WithObjects(objs...).Do(ctx) if err != nil { return nil, fmt.Errorf("make insertion request: %w", err) } for _, res := range resp { if res.Result != nil && res.Result.Errors != nil && res.Result.Errors.Error != nil { for _, nestedErr := range res.Result.Errors.Error { err = errors.Join(err, errors.New(nestedErr.Message)) } } } return resp, err } ``` -------------------------------------------------------------------------------- /mcp.go: -------------------------------------------------------------------------------- ```go package main import ( "context" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) type MCPServer struct { server *server.MCPServer weaviateConn *WeaviateConnection defaultCollection string } func NewMCPServer() (*MCPServer, error) { conn, err := NewWeaviateConnection() if err != nil { return nil, err } s := &MCPServer{ server: server.NewMCPServer( "Weaviate MCP Server", "0.1.0", server.WithToolCapabilities(true), server.WithPromptCapabilities(true), server.WithResourceCapabilities(true, true), server.WithRecovery(), ), weaviateConn: conn, // TODO: configurable collection name defaultCollection: "DefaultCollection", } s.registerTools() return s, nil } func (s *MCPServer) Serve() { server.ServeStdio(s.server) } func (s *MCPServer) registerTools() { insertOne := mcp.NewTool( "weaviate-insert-one", mcp.WithString( "collection", mcp.Description("Name of the target collection"), ), mcp.WithObject( "properties", mcp.Description("Object properties to insert"), mcp.Required(), ), ) query := mcp.NewTool( "weaviate-query", mcp.WithString( "query", mcp.Description("Query data within Weaviate"), mcp.Required(), ), mcp.WithArray( "targetProperties", mcp.Description("Properties to return with the query"), mcp.Required(), ), ) s.server.AddTools( server.ServerTool{Tool: insertOne, Handler: s.weaviateInsertOne}, server.ServerTool{Tool: query, Handler: s.weaviateQuery}, ) } func (s *MCPServer) weaviateInsertOne(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { targetCol := s.parseTargetCollection(req) props := req.Params.Arguments["properties"].(map[string]interface{}) res, err := s.weaviateConn.InsertOne(context.Background(), targetCol, props) if err != nil { return mcp.NewToolResultErrorFromErr("failed to insert object", err), nil } return mcp.NewToolResultText(res.ID.String()), nil } func (s *MCPServer) weaviateQuery(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { targetCol := s.parseTargetCollection(req) query := req.Params.Arguments["query"].(string) // TODO: how to enforce `Required` within the sdk so we don't have to validate here props := req.Params.Arguments["targetProperties"].([]interface{}) var targetProps []string { for _, prop := range props { typed, ok := prop.(string) if !ok { return mcp.NewToolResultError("targetProperties must contain only strings"), nil } targetProps = append(targetProps, typed) } } res, err := s.weaviateConn.Query(context.Background(), targetCol, query, targetProps) if err != nil { return mcp.NewToolResultErrorFromErr("failed to process query", err), nil } return mcp.NewToolResultText(res), nil } func (s *MCPServer) parseTargetCollection(req mcp.CallToolRequest) string { var ( targetCol = s.defaultCollection ) col, ok := req.Params.Arguments["collection"].(string) if ok { targetCol = col } return targetCol } ```