Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,26 @@ linters:
- whitespace

settings:

gomoddirectives:
replace-allow-list:
- github.com/sirupsen/logrus

ireturn:
allow:
- error
- db.Error
- logic.Error

mnd:
ignored-files:
- 'internal\\config\\.+\.go'

varnamelen:
ignore-decls:
- c *gin.Context
ignore-names:
- up


run:
issues-exit-code: 1
32 changes: 5 additions & 27 deletions cmd/mcp_dbmem/action/direct/direct.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ import (

// Direct is the action to start the mcp server with a direct connection to the database.
var Direct action.Action = func(ctx context.Context, _ []string) error {
zap.L().Info("starting pgmcp")
zap.L().Info("starting " + config.ApplicationName)

// Setup tracing
if viper.GetString(config.Keys.UptraceDSN) != "" {
uptrace.ConfigureOpentelemetry(
uptrace.WithServiceName("mcp-dbmem"),
uptrace.WithServiceName(config.ApplicationName),
uptrace.WithServiceVersion(viper.GetString(config.Keys.SoftwareVersion)),
uptrace.WithDSN(viper.GetString(config.Keys.UptraceDSN)),
)
Expand Down Expand Up @@ -70,31 +70,9 @@ var Direct action.Action = func(ctx context.Context, _ []string) error {

// add tools
server := mcp.NewServer(stdio.NewStdioServerTransport())
if err := server.RegisterTool("create_entities", "Create multiple new entities in the knowledge graph", direct.CreateEntities); err != nil {
return err
}
if err := server.RegisterTool("create_relations", "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice", direct.CreateRelations); err != nil {
return err
}
if err := server.RegisterTool("add_observations", "Add new observations to existing entities in the knowledge graph", direct.AddObservations); err != nil {
return err
}
if err := server.RegisterTool("delete_entities", "Delete multiple entities and their associated relations from the knowledge graph", direct.DeleteEntities); err != nil {
return err
}
if err := server.RegisterTool("delete_observations", "Delete specific observations from entities in the knowledge graph", direct.DeleteObservations); err != nil {
return err
}
if err := server.RegisterTool("delete_relations", "Delete multiple relations from the knowledge graph", direct.DeleteRelations); err != nil {
return err
}
if err := server.RegisterTool("read_graph", "Read the entire knowledge graph", direct.ReadGraph); err != nil {
return err
}
if err := server.RegisterTool("search_nodes", "Search for nodes in the knowledge graph based on a query", direct.SearchNodes); err != nil {
return err
}
if err := server.RegisterTool("open_nodes", "Open specific nodes in the knowledge graph by their names", direct.OpenNodes); err != nil {
if err := direct.Apply(server); err != nil {
zap.L().Error("Error applying direct adapter", zap.Error(err))

return err
}

Expand Down
117 changes: 117 additions & 0 deletions cmd/mcp_dbmem/action/server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package server

import (
"context"
"fmt"
"os"
"os/signal"
"syscall"

"github.com/spf13/viper"
"github.com/tyrm/mcp-dbmem/cmd/mcp_dbmem/action"
"github.com/tyrm/mcp-dbmem/internal/config"
"github.com/tyrm/mcp-dbmem/internal/db/bun"
"github.com/tyrm/mcp-dbmem/internal/http"
"github.com/tyrm/mcp-dbmem/internal/http/apiv1"
v1 "github.com/tyrm/mcp-dbmem/internal/logic/v1"
"github.com/uptrace/uptrace-go/uptrace"
"go.uber.org/zap"
)

// Server is the action to start the mcp server with a direct connection to the database.
var Server action.Action = func(ctx context.Context, _ []string) error {
zap.L().Info(fmt.Sprintf("starting %s server", config.ApplicationName))
ctx, cancel := context.WithCancel(ctx)

// Setup tracing
if viper.GetString(config.Keys.UptraceDSN) != "" {
uptrace.ConfigureOpentelemetry(
uptrace.WithServiceName(config.ApplicationName),
uptrace.WithServiceVersion(viper.GetString(config.Keys.SoftwareVersion)),
uptrace.WithDSN(viper.GetString(config.Keys.UptraceDSN)),
)
// Send buffered spans and free resources.
defer func() {
if err := uptrace.Shutdown(context.Background()); err != nil {
zap.L().Error("Error shutting down uptrace", zap.Error(err))
}
}()
}

// create database client
dbClient, err := bun.New(ctx, bun.ClientConfig{
Type: viper.GetString(config.Keys.DBType),
Address: viper.GetString(config.Keys.DBAddress),
Port: viper.GetUint16(config.Keys.DBPort),
User: viper.GetString(config.Keys.DBUser),
Password: viper.GetString(config.Keys.DBPassword),
Database: viper.GetString(config.Keys.DBDatabase),
TLSMode: viper.GetString(config.Keys.DBTLSMode),
TLSCACert: viper.GetString(config.Keys.DBTLSCACert),
})
if err != nil {
zap.L().Error("Error creating bun client", zap.Error(err))
cancel()

return err
}
defer func() {
if err := dbClient.Close(); err != nil {
zap.L().Error("Error closing bun client", zap.Error(err))
}
}()

// build logic
logic := v1.NewLogic(v1.LogicConfig{
DB: dbClient,
})

httpServer, err := http.NewServer(ctx, http.ServerConfig{
Logic: logic,
ApplicationName: config.ApplicationName,
HttpBind: ":4200",
})
if err != nil {
zap.L().Error("can't start http server", zap.Error(err))
cancel()
return err
}
// create web modules
var webModules = make([]http.Module, 0)

zap.L().Info("loading apiv1 module")
apiV1 := apiv1.New(logic)
webModules = append(webModules, apiV1)

// add modules to server
for _, mod := range webModules {
mod.Route(httpServer)
}
// ** start application **
errChan := make(chan error)

// Wait for SIGINT and SIGTERM (HIT CTRL-C)
stopSigChan := make(chan os.Signal, 1)
signal.Notify(stopSigChan, syscall.SIGINT, syscall.SIGTERM)

// start webserver
go func(s *http.Server, errChan chan error) {
zap.L().Info("starting http server")
err := s.Start()
if err != nil {
errChan <- fmt.Errorf("http server: %s", err.Error())
}
}(httpServer, errChan)

// wait for event
select {
case sig := <-stopSigChan:
zap.L().Info("got signal", zap.String("signal", sig.String()))
case err := <-errChan:
zap.L().Fatal("fatal error", zap.Error(err))
}

cancel()
zap.L().Info("done")
return nil
}
11 changes: 11 additions & 0 deletions cmd/mcp_dbmem/flag/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package flag

import (
"github.com/spf13/cobra"
"github.com/tyrm/mcp-dbmem/internal/config"
)

// Server adds flags for the server command.
func Server(cmd *cobra.Command, values config.Values) {
Database(cmd, values)
}
68 changes: 49 additions & 19 deletions cmd/mcp_dbmem/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/tyrm/mcp-dbmem/cmd/mcp_dbmem/action"
"github.com/tyrm/mcp-dbmem/cmd/mcp_dbmem/action/direct"
"github.com/tyrm/mcp-dbmem/cmd/mcp_dbmem/action/migrate"
"github.com/tyrm/mcp-dbmem/cmd/mcp_dbmem/action/server"
"github.com/tyrm/mcp-dbmem/cmd/mcp_dbmem/flag"
"github.com/tyrm/mcp-dbmem/internal/config"
"go.uber.org/zap"
Expand Down Expand Up @@ -46,15 +47,39 @@ func main() {
viper.Set(config.Keys.SoftwareVersion, version)

rootCmd := &cobra.Command{
Use: "mcp-dbmem",
Use: config.ApplicationName,
Short: "", // TODO
Version: version,
SilenceErrors: true,
SilenceUsage: true,
}
flag.Global(rootCmd, config.Defaults)

directCmd := &cobra.Command{
rootCmd.AddCommand(directCommands())
rootCmd.AddCommand(migrateCommands())
rootCmd.AddCommand(serverCommands())

err = rootCmd.Execute()
if err != nil {
zap.L().Fatal("Error executing command", zap.Error(err))
}
}

func preRun(cmd *cobra.Command) error {
if err := config.Init(cmd.Flags()); err != nil {
return fmt.Errorf("error initializing config: %w", err)
}

return nil
}

func run(ctx context.Context, action action.Action, args []string) error {
return action(ctx, args)
}

// directCommands returns the 'direct' subcommand.
func directCommands() *cobra.Command {
rootCmd := &cobra.Command{
Use: "direct",
Short: "the mcp server will connect directly to the database",
PreRunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -64,10 +89,14 @@ func main() {
return run(cmd.Context(), direct.Direct, args)
},
}
flag.Direct(directCmd, config.Defaults)
rootCmd.AddCommand(directCmd)
flag.Direct(rootCmd, config.Defaults)

migrateCmd := &cobra.Command{
return rootCmd
}

// migrateCommands returns the 'migrate' subcommand.
func migrateCommands() *cobra.Command {
rootCmd := &cobra.Command{
Use: "migrate",
Short: "run db migrations",
PreRunE: func(cmd *cobra.Command, _ []string) error {
Expand All @@ -77,23 +106,24 @@ func main() {
return run(cmd.Context(), migrate.Migrate, args)
},
}
flag.Migrate(migrateCmd, config.Defaults)
rootCmd.AddCommand(migrateCmd)
flag.Migrate(rootCmd, config.Defaults)

err = rootCmd.Execute()
if err != nil {
zap.L().Fatal("Error executing command", zap.Error(err))
}
return rootCmd
}

func preRun(cmd *cobra.Command) error {
if err := config.Init(cmd.Flags()); err != nil {
return fmt.Errorf("error initializing config: %w", err)
// serverCommands returns the 'server' subcommand.
func serverCommands() *cobra.Command {
serverCmd := &cobra.Command{
Use: "server",
Short: "start the server",
PreRunE: func(cmd *cobra.Command, args []string) error {
return preRun(cmd)
},
RunE: func(cmd *cobra.Command, args []string) error {
return run(cmd.Context(), server.Server, args)
},
}
flag.Server(serverCmd, config.Defaults)

return nil
}

func run(ctx context.Context, action action.Action, args []string) error {
return action(ctx, args)
return serverCmd
}
27 changes: 23 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ module github.com/tyrm/mcp-dbmem
go 1.24

replace (
// Pinning to v1.10.0 to address a vulnerability in the gin-gonic/gin package.
github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.10.0
// Pinning to v1.9.3 to address a vulnerability in the github.com/jackc/pgx.
github.com/jackc/pgx/v4 => github.com/jackc/pgx/v4 v4.18.3
// Pinning to v1.9.3 to address a vulnerability in the github.com/sirupsen/logrus package.
github.com/sirupsen/logrus => github.com/sirupsen/logrus v1.9.3
)

require (
github.com/gin-contrib/sessions v1.0.3
github.com/gin-gonic/gin v1.10.1
github.com/go-sql-driver/mysql v1.9.2
github.com/jackc/pgconn v1.14.3
github.com/jackc/pgx/v4 v4.18.3
Expand All @@ -26,6 +24,7 @@ require (
github.com/uptrace/bun/dialect/sqlitedialect v1.2.11
github.com/uptrace/bun/extra/bunotel v1.2.11
github.com/uptrace/uptrace-go v1.35.1
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0
go.opentelemetry.io/otel v1.35.0
go.opentelemetry.io/otel/trace v1.35.0
go.uber.org/zap v1.27.0
Expand All @@ -37,14 +36,26 @@ require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/bytedance/sonic v1.13.2 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
Expand All @@ -55,8 +66,13 @@ require (
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgtype v1.14.4 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
Expand All @@ -73,6 +89,8 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
Expand All @@ -91,6 +109,7 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.16.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/mod v0.24.0 // indirect
Expand Down
Loading