#
tokens: 3469/50000 6/6 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── client
│   └── client.go
├── go.mod
├── go.sum
├── main.go
├── Makefile
├── mcp.go
├── README.md
└── weaviate.go
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | */mcp-server
2 | */.idea/*
3 | 
4 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Weaviate MCP Server
 2 | 
 3 | ## Instructions
 4 | 
 5 | Build the server:
 6 | ```
 7 | make build
 8 | ```
 9 | 
10 | Run the test client
11 | ```
12 | make run-client
13 | ```
14 | 
15 | ## Tools
16 | 
17 | ### Insert One
18 | Insert an object into weaviate.
19 | 
20 | **Request body:**
21 | ```json
22 | {}
23 | ```
24 | 
25 | **Response body**
26 | ```json
27 | {}
28 | ```
29 | 
30 | ### Query
31 | Retrieve objects from weaviate with hybrid search.
32 | 
33 | **Request body:**
34 | ```json
35 | {}
36 | ```
37 | 
38 | **Response body**
39 | ```json
40 | {}
41 | ```
42 | 
```

--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------

```go
 1 | package main
 2 | 
 3 | import (
 4 | 	"log"
 5 | )
 6 | 
 7 | func main() {
 8 | 	// TODO: get all WeaviateConn config from env
 9 | 	// TODO: support SSEs
10 | 	// var transport string
11 | 	// flag.StringVar(&transport, "transport", "stdio", "Specifies the transport protocol. One of [stdio|sse]")
12 | 	s, err := NewMCPServer()
13 | 	if err != nil {
14 | 		log.Fatalf("failed to start mcp server: %v", err)
15 | 	}
16 | 	_ = s
17 | 	s.Serve()
18 | }
19 | 
```

--------------------------------------------------------------------------------
/client/client.go:
--------------------------------------------------------------------------------

```go
 1 | package main
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 	"log"
 7 | 
 8 | 	"github.com/mark3labs/mcp-go/client"
 9 | 	"github.com/mark3labs/mcp-go/mcp"
10 | )
11 | 
12 | func main() {
13 | 	ctx := context.Background()
14 | 	cmd := "./mcp-server"
15 | 
16 | 	c, err := newMCPClient(ctx, cmd)
17 | 	if err != nil {
18 | 		log.Fatal(err)
19 | 	}
20 | 	defer c.Close()
21 | 
22 | 	{
23 | 		insertRes, err := insertRequest(ctx, c)
24 | 		if err != nil {
25 | 			log.Fatal(err)
26 | 		}
27 | 		log.Printf("insert-one response: %+v", insertRes)
28 | 	}
29 | 	{
30 | 		queryRes, err := queryRequest(ctx, c)
31 | 		if err != nil {
32 | 			log.Fatal(err)
33 | 		}
34 | 		log.Printf("query response: %+v", queryRes)
35 | 	}
36 | }
37 | 
38 | func newMCPClient(ctx context.Context, cmd string) (*client.Client, error) {
39 | 	c, _ := client.NewStdioMCPClient(cmd, nil)
40 | 	initRes, err := c.Initialize(ctx, mcp.InitializeRequest{})
41 | 	if err != nil {
42 | 		return nil, fmt.Errorf("failed to init client: %w", err)
43 | 	}
44 | 	log.Printf("init result: %+v", initRes)
45 | 	if err := c.Start(ctx); err != nil {
46 | 		return nil, fmt.Errorf("failed to start client: %w", err)
47 | 	}
48 | 	if err := c.Ping(ctx); err != nil {
49 | 		return nil, fmt.Errorf("failed to ping server: %w", err)
50 | 	}
51 | 	return c, nil
52 | }
53 | 
54 | func insertRequest(ctx context.Context, c *client.Client) (*mcp.CallToolResult, error) {
55 | 	request := mcp.CallToolRequest{}
56 | 	request.Params.Name = "weaviate-insert-one"
57 | 	request.Params.Arguments = map[string]interface{}{
58 | 		"collection": "WorldMap",
59 | 		"properties": map[string]interface{}{
60 | 			"continent": "Europe",
61 | 			"country":   "Spain",
62 | 			"city":      "Valencia",
63 | 		},
64 | 	}
65 | 	log.Printf("insert request: %+v", request)
66 | 	res, err := c.CallTool(ctx, request)
67 | 	if err != nil {
68 | 		return nil, fmt.Errorf("failed to call insert-one tool: %v", err)
69 | 	}
70 | 	return res, nil
71 | }
72 | 
73 | func queryRequest(ctx context.Context, c *client.Client) (*mcp.CallToolResult, error) {
74 | 	request := mcp.CallToolRequest{}
75 | 	request.Params.Name = "weaviate-query"
76 | 	request.Params.Arguments = map[string]interface{}{
77 | 		"collection":       "WorldMap",
78 | 		"query":            "What country is Valencia in?",
79 | 		"targetProperties": []string{"continent", "country", "city"},
80 | 	}
81 | 	log.Printf("query request: %+v", request)
82 | 	res, err := c.CallTool(ctx, request)
83 | 	if err != nil {
84 | 		return nil, fmt.Errorf("failed to call query tool: %v", err)
85 | 	}
86 | 	return res, nil
87 | }
88 | 
```

--------------------------------------------------------------------------------
/weaviate.go:
--------------------------------------------------------------------------------

```go
 1 | package main
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"encoding/json"
 6 | 	"errors"
 7 | 	"fmt"
 8 | 	"time"
 9 | 
10 | 	"github.com/weaviate/weaviate-go-client/v4/weaviate"
11 | 	"github.com/weaviate/weaviate-go-client/v4/weaviate/graphql"
12 | 	"github.com/weaviate/weaviate/entities/models"
13 | )
14 | 
15 | type WeaviateConnection struct {
16 | 	client *weaviate.Client
17 | }
18 | 
19 | func NewWeaviateConnection() (*WeaviateConnection, error) {
20 | 	// TODO: get config from env
21 | 	client, err := weaviate.NewClient(weaviate.Config{
22 | 		Host:           "localhost:8080",
23 | 		Scheme:         "http",
24 | 		StartupTimeout: time.Second,
25 | 	})
26 | 	if err != nil {
27 | 		return nil, fmt.Errorf("connect to weaviate: %w", err)
28 | 	}
29 | 	return &WeaviateConnection{client}, nil
30 | }
31 | 
32 | func (conn *WeaviateConnection) InsertOne(ctx context.Context,
33 | 	collection string, props interface{},
34 | ) (*models.Object, error) {
35 | 	obj := models.Object{
36 | 		Class:      collection,
37 | 		Properties: props,
38 | 	}
39 | 	// Use batch to leverage autoschema and gRPC
40 | 	resp, err := conn.batchInsert(ctx, &obj)
41 | 	if err != nil {
42 | 		return nil, fmt.Errorf("insert one object: %w", err)
43 | 	}
44 | 
45 | 	return &resp[0].Object, err
46 | }
47 | 
48 | func (conn *WeaviateConnection) Query(ctx context.Context, collection,
49 | 	query string, targetProps []string,
50 | ) (string, error) {
51 | 	hybrid := graphql.HybridArgumentBuilder{}
52 | 	hybrid.WithQuery(query)
53 | 	res, err := conn.client.GraphQL().Get().
54 | 		WithClassName(collection).WithHybrid(&hybrid).
55 | 		WithFields(func() []graphql.Field {
56 | 			fields := make([]graphql.Field, len(targetProps))
57 | 			for i, prop := range targetProps {
58 | 				fields[i] = graphql.Field{Name: prop}
59 | 			}
60 | 			return fields
61 | 		}()...).
62 | 		Do(context.Background())
63 | 	if err != nil {
64 | 		return "", err
65 | 	}
66 | 	b, err := json.Marshal(res)
67 | 	if err != nil {
68 | 		return "", fmt.Errorf("unmarshal query response: %w", err)
69 | 	}
70 | 	return string(b), nil
71 | }
72 | 
73 | func (conn *WeaviateConnection) batchInsert(ctx context.Context, objs ...*models.Object) ([]models.ObjectsGetResponse, error) {
74 | 	resp, err := conn.client.Batch().ObjectsBatcher().WithObjects(objs...).Do(ctx)
75 | 	if err != nil {
76 | 		return nil, fmt.Errorf("make insertion request: %w", err)
77 | 	}
78 | 	for _, res := range resp {
79 | 		if res.Result != nil && res.Result.Errors != nil && res.Result.Errors.Error != nil {
80 | 			for _, nestedErr := range res.Result.Errors.Error {
81 | 				err = errors.Join(err, errors.New(nestedErr.Message))
82 | 			}
83 | 		}
84 | 	}
85 | 
86 | 	return resp, err
87 | }
88 | 
```

--------------------------------------------------------------------------------
/mcp.go:
--------------------------------------------------------------------------------

```go
  1 | package main
  2 | 
  3 | import (
  4 | 	"context"
  5 | 
  6 | 	"github.com/mark3labs/mcp-go/mcp"
  7 | 	"github.com/mark3labs/mcp-go/server"
  8 | )
  9 | 
 10 | type MCPServer struct {
 11 | 	server            *server.MCPServer
 12 | 	weaviateConn      *WeaviateConnection
 13 | 	defaultCollection string
 14 | }
 15 | 
 16 | func NewMCPServer() (*MCPServer, error) {
 17 | 	conn, err := NewWeaviateConnection()
 18 | 	if err != nil {
 19 | 		return nil, err
 20 | 	}
 21 | 	s := &MCPServer{
 22 | 		server: server.NewMCPServer(
 23 | 			"Weaviate MCP Server",
 24 | 			"0.1.0",
 25 | 			server.WithToolCapabilities(true),
 26 | 			server.WithPromptCapabilities(true),
 27 | 			server.WithResourceCapabilities(true, true),
 28 | 			server.WithRecovery(),
 29 | 		),
 30 | 		weaviateConn: conn,
 31 | 		// TODO: configurable collection name
 32 | 		defaultCollection: "DefaultCollection",
 33 | 	}
 34 | 	s.registerTools()
 35 | 	return s, nil
 36 | }
 37 | 
 38 | func (s *MCPServer) Serve() {
 39 | 	server.ServeStdio(s.server)
 40 | }
 41 | 
 42 | func (s *MCPServer) registerTools() {
 43 | 	insertOne := mcp.NewTool(
 44 | 		"weaviate-insert-one",
 45 | 		mcp.WithString(
 46 | 			"collection",
 47 | 			mcp.Description("Name of the target collection"),
 48 | 		),
 49 | 		mcp.WithObject(
 50 | 			"properties",
 51 | 			mcp.Description("Object properties to insert"),
 52 | 			mcp.Required(),
 53 | 		),
 54 | 	)
 55 | 	query := mcp.NewTool(
 56 | 		"weaviate-query",
 57 | 		mcp.WithString(
 58 | 			"query",
 59 | 			mcp.Description("Query data within Weaviate"),
 60 | 			mcp.Required(),
 61 | 		),
 62 | 		mcp.WithArray(
 63 | 			"targetProperties",
 64 | 			mcp.Description("Properties to return with the query"),
 65 | 			mcp.Required(),
 66 | 		),
 67 | 	)
 68 | 
 69 | 	s.server.AddTools(
 70 | 		server.ServerTool{Tool: insertOne, Handler: s.weaviateInsertOne},
 71 | 		server.ServerTool{Tool: query, Handler: s.weaviateQuery},
 72 | 	)
 73 | }
 74 | 
 75 | func (s *MCPServer) weaviateInsertOne(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 76 | 	targetCol := s.parseTargetCollection(req)
 77 | 	props := req.Params.Arguments["properties"].(map[string]interface{})
 78 | 
 79 | 	res, err := s.weaviateConn.InsertOne(context.Background(), targetCol, props)
 80 | 	if err != nil {
 81 | 		return mcp.NewToolResultErrorFromErr("failed to insert object", err), nil
 82 | 	}
 83 | 	return mcp.NewToolResultText(res.ID.String()), nil
 84 | }
 85 | 
 86 | func (s *MCPServer) weaviateQuery(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 87 | 	targetCol := s.parseTargetCollection(req)
 88 | 	query := req.Params.Arguments["query"].(string)
 89 | 	// TODO: how to enforce `Required` within the sdk so we don't have to validate here
 90 | 	props := req.Params.Arguments["targetProperties"].([]interface{})
 91 | 	var targetProps []string
 92 | 	{
 93 | 		for _, prop := range props {
 94 | 			typed, ok := prop.(string)
 95 | 			if !ok {
 96 | 				return mcp.NewToolResultError("targetProperties must contain only strings"), nil
 97 | 			}
 98 | 			targetProps = append(targetProps, typed)
 99 | 		}
100 | 	}
101 | 	res, err := s.weaviateConn.Query(context.Background(), targetCol, query, targetProps)
102 | 	if err != nil {
103 | 		return mcp.NewToolResultErrorFromErr("failed to process query", err), nil
104 | 	}
105 | 	return mcp.NewToolResultText(res), nil
106 | }
107 | 
108 | func (s *MCPServer) parseTargetCollection(req mcp.CallToolRequest) string {
109 | 	var (
110 | 		targetCol = s.defaultCollection
111 | 	)
112 | 	col, ok := req.Params.Arguments["collection"].(string)
113 | 	if ok {
114 | 		targetCol = col
115 | 	}
116 | 	return targetCol
117 | }
118 | 
```