#
tokens: 2435/50000 6/6 files
lines: off (toggle) GitHub
raw markdown copy
# 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
}

```