mirror of
https://github.com/dolthub/dolt.git
synced 2026-04-28 04:28:53 -05:00
/{go,integration-tests}: rebase main
This commit is contained in:
committed by
coffeegoddd☕️✨
parent
4ae0bf48d7
commit
700e985978
@@ -0,0 +1,196 @@
|
||||
// Copyright 2025 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sqlserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
pkgmcp "github.com/dolthub/dolt-mcp/mcp/pkg"
|
||||
mcpdb "github.com/dolthub/dolt-mcp/mcp/pkg/db"
|
||||
"github.com/dolthub/dolt-mcp/mcp/pkg/toolsets"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/svcs"
|
||||
)
|
||||
|
||||
// MCPConfig encapsulates MCP-specific configuration for the sql-server
|
||||
type MCPConfig struct {
|
||||
Port *int
|
||||
User *string
|
||||
Password *string
|
||||
Database *string
|
||||
}
|
||||
|
||||
// logrusZapCore implements a zapcore.Core that forwards entries to a logrus.Logger
|
||||
type logrusZapCore struct {
|
||||
l *logrus.Logger
|
||||
prefix string
|
||||
}
|
||||
|
||||
func newZapFromLogrusWithPrefix(l *logrus.Logger, prefix string) *zap.Logger {
|
||||
core := &logrusZapCore{l: l, prefix: prefix}
|
||||
return zap.New(core, zap.AddCallerSkip(1))
|
||||
}
|
||||
|
||||
func (c *logrusZapCore) With(fields []zapcore.Field) zapcore.Core {
|
||||
// zap will call Write with fields; we don't need to carry state here
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *logrusZapCore) Enabled(lvl zapcore.Level) bool {
|
||||
// Respect logrus current level
|
||||
return zapToLogrusLevel(lvl) >= c.l.GetLevel()
|
||||
}
|
||||
|
||||
func (c *logrusZapCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||
if c.Enabled(ent.Level) {
|
||||
return ce.AddCore(ent, c)
|
||||
}
|
||||
return ce
|
||||
}
|
||||
|
||||
func (c *logrusZapCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {
|
||||
enc := zapcore.NewMapObjectEncoder()
|
||||
for _, f := range fields {
|
||||
f.AddTo(enc)
|
||||
}
|
||||
// Build fields map
|
||||
lf := logrus.Fields(enc.Fields)
|
||||
msg := c.prefix + ent.Message
|
||||
c.l.WithFields(lf).Log(zapToLogrusLevel(ent.Level), msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *logrusZapCore) Sync() error { return nil }
|
||||
|
||||
func zapToLogrusLevel(l zapcore.Level) logrus.Level {
|
||||
switch l {
|
||||
case zapcore.DebugLevel:
|
||||
return logrus.DebugLevel
|
||||
case zapcore.InfoLevel:
|
||||
return logrus.InfoLevel
|
||||
case zapcore.WarnLevel:
|
||||
return logrus.WarnLevel
|
||||
case zapcore.ErrorLevel:
|
||||
return logrus.ErrorLevel
|
||||
case zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel:
|
||||
return logrus.FatalLevel
|
||||
default:
|
||||
return logrus.InfoLevel
|
||||
}
|
||||
}
|
||||
|
||||
// mcpInit validates and reserves the MCP port
|
||||
func mcpInit(cfg *Config, state *svcs.ServiceState) func(context.Context) error {
|
||||
return func(context.Context) error {
|
||||
addr := net.JoinHostPort("0.0.0.0", strconv.Itoa(*cfg.MCP.Port))
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = l.Close()
|
||||
state.Swap(svcs.ServiceState_Init)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// mcpRun starts the MCP HTTP server and wires lifecycle to errgroup
|
||||
func mcpRun(cfg *Config, lgr *logrus.Logger, state *svcs.ServiceState, cancelPtr *context.CancelFunc, groupPtr **errgroup.Group) func(context.Context) {
|
||||
return func(ctx context.Context) {
|
||||
if !state.CompareAndSwap(svcs.ServiceState_Init, svcs.ServiceState_Run) {
|
||||
return
|
||||
}
|
||||
|
||||
// Logger for MCP (prefix and level inherited from logrus)
|
||||
logger := newZapFromLogrusWithPrefix(lgr, "[dolt-mcp] ")
|
||||
|
||||
if cfg.MCP.User == nil || *cfg.MCP.User == "" {
|
||||
lgr.WithField("component", "dolt-mcp").Error("MCP user not defined")
|
||||
return
|
||||
}
|
||||
|
||||
// Build DB config
|
||||
password := ""
|
||||
if cfg.MCP.Password != nil {
|
||||
password = *cfg.MCP.Password
|
||||
}
|
||||
dbName := ""
|
||||
if cfg.MCP.Database != nil {
|
||||
dbName = *cfg.MCP.Database
|
||||
}
|
||||
dbConf := mcpdb.Config{
|
||||
Host: "127.0.0.1",
|
||||
Port: cfg.ServerConfig.Port(),
|
||||
User: *cfg.MCP.User,
|
||||
Password: password,
|
||||
DatabaseName: dbName,
|
||||
}
|
||||
|
||||
srv, err := pkgmcp.NewMCPHTTPServer(
|
||||
logger,
|
||||
dbConf,
|
||||
*cfg.MCP.Port,
|
||||
toolsets.WithToolSet(&toolsets.PrimitiveToolSetV1{}),
|
||||
)
|
||||
if err != nil {
|
||||
lgr.WithField("component", "dolt-mcp").Errorf("failed to start Dolt MCP HTTP server: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
runCtx, cancel := context.WithCancel(ctx)
|
||||
*cancelPtr = cancel
|
||||
g, gctx := errgroup.WithContext(runCtx)
|
||||
g.Go(func() error { srv.ListenAndServe(gctx); return nil })
|
||||
*groupPtr = g
|
||||
}
|
||||
}
|
||||
|
||||
// mcpStop gracefully stops the MCP server by cancelling context and waiting for the errgroup
|
||||
func mcpStop(cancel context.CancelFunc, group *errgroup.Group, state *svcs.ServiceState) func() error {
|
||||
return func() error {
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
if group != nil {
|
||||
if err := group.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
state.Swap(svcs.ServiceState_Stopped)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// registerMCPService wires the MCP service into the controller using helper funcs
|
||||
func registerMCPService(controller *svcs.Controller, cfg *Config, lgr *logrus.Logger) {
|
||||
if cfg.MCP == nil || cfg.MCP.Port == nil || *cfg.MCP.Port <= 0 {
|
||||
return
|
||||
}
|
||||
var state svcs.ServiceState
|
||||
var cancel context.CancelFunc
|
||||
var group *errgroup.Group
|
||||
|
||||
svc := &svcs.AnonService{
|
||||
InitF: mcpInit(cfg, &state),
|
||||
RunF: mcpRun(cfg, lgr, &state, &cancel, &group),
|
||||
StopF: mcpStop(cancel, group, &state),
|
||||
}
|
||||
_ = controller.Register(svc)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2025 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sqlserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/dconfig"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/servercfg"
|
||||
)
|
||||
|
||||
// validateAndPrepareMCP performs coherence checks for MCP options and fails fast
|
||||
// when the specified port is unavailable. It also sets DOLT_ROOT_HOST dynamically
|
||||
// when MCP is enabled and the env var is not already set.
|
||||
func validateAndPrepareMCP(
|
||||
serverConfig servercfg.ServerConfig,
|
||||
mcpPort *int,
|
||||
mcpUser *string,
|
||||
mcpPassword *string,
|
||||
mcpDatabase *string,
|
||||
) error {
|
||||
// If any MCP-related arg is supplied without --mcp-port, error
|
||||
if mcpPort == nil {
|
||||
if mcpUser != nil && *mcpUser != "" {
|
||||
return fmt.Errorf("--%s requires --%s to be set", mcpUserFlag, mcpPortFlag)
|
||||
}
|
||||
if mcpPassword != nil && *mcpPassword != "" {
|
||||
return fmt.Errorf("--%s requires --%s to be set", mcpPasswordFlag, mcpPortFlag)
|
||||
}
|
||||
if mcpDatabase != nil && *mcpDatabase != "" {
|
||||
return fmt.Errorf("--%s requires --%s to be set", mcpDatabaseFlag, mcpPortFlag)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --mcp-user is REQUIRED when --mcp-port is provided
|
||||
if mcpUser == nil || *mcpUser == "" {
|
||||
return fmt.Errorf("--%s is required when --%s is specified", mcpUserFlag, mcpPortFlag)
|
||||
}
|
||||
|
||||
// Range and conflict checks
|
||||
if *mcpPort <= 0 || *mcpPort > 65535 {
|
||||
return fmt.Errorf("invalid value for --%s '%d'", mcpPortFlag, *mcpPort)
|
||||
}
|
||||
if serverConfig.Port() == *mcpPort {
|
||||
return fmt.Errorf("--%s must differ from --%s (both set to %d)", mcpPortFlag, portFlag, *mcpPort)
|
||||
}
|
||||
|
||||
// If MCP is enabled and no explicit root host override exists, set DOLT_ROOT_HOST dynamically
|
||||
// to the SQL listener host, defaulting to 127.0.0.1 when unspecified or wildcard.
|
||||
if _, ok := os.LookupEnv(dconfig.EnvDoltRootHost); !ok {
|
||||
hostForRoot := serverConfig.Host()
|
||||
if hostForRoot == "" || hostForRoot == "0.0.0.0" || hostForRoot == "::" {
|
||||
hostForRoot = "127.0.0.1"
|
||||
}
|
||||
_ = os.Setenv(dconfig.EnvDoltRootHost, hostForRoot)
|
||||
}
|
||||
|
||||
// Preflight port availability to fail fast before starting controller
|
||||
addr := net.JoinHostPort("0.0.0.0", strconv.Itoa(*mcpPort))
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("MCP port %d already in use: %v", *mcpPort, err)
|
||||
}
|
||||
_ = l.Close()
|
||||
return nil
|
||||
}
|
||||
@@ -84,6 +84,7 @@ type Config struct {
|
||||
Version string
|
||||
Controller *svcs.Controller
|
||||
ProtocolListenerFactory server.ProtocolListenerFunc
|
||||
MCP *MCPConfig
|
||||
}
|
||||
|
||||
// Serve starts a MySQL-compatible server. Returns any errors that were encountered.
|
||||
@@ -866,6 +867,9 @@ func ConfigureServices(
|
||||
},
|
||||
}
|
||||
controller.Register(RunSQLServer)
|
||||
|
||||
// Optionally start an MCP HTTP server
|
||||
registerMCPService(controller, cfg, lgr)
|
||||
}
|
||||
|
||||
// heartbeatService is a service that sends a heartbeat event to the metrics server once a day
|
||||
|
||||
@@ -56,6 +56,10 @@ const (
|
||||
remotesapiReadOnlyFlag = "remotesapi-readonly"
|
||||
goldenMysqlConn = "golden"
|
||||
eventSchedulerStatus = "event-scheduler"
|
||||
mcpPortFlag = "mcp-port"
|
||||
mcpUserFlag = "mcp-user"
|
||||
mcpPasswordFlag = "mcp-password"
|
||||
mcpDatabaseFlag = "mcp-database"
|
||||
)
|
||||
|
||||
func indentLines(s string) string {
|
||||
@@ -195,6 +199,12 @@ func (cmd SqlServerCmd) ArgParserWithName(name string) *argparser.ArgParser {
|
||||
ap.SupportsFlag(remotesapiReadOnlyFlag, "", "Disable writes to the sql-server via the push operations. SQL writes are unaffected by this setting.")
|
||||
ap.SupportsString(goldenMysqlConn, "", "mysql connection string", "Provides a connection string to a MySQL instance to be used to validate query results")
|
||||
ap.SupportsString(eventSchedulerStatus, "", "status", "Determines whether the Event Scheduler is enabled and running on the server. It has one of the following values: 'ON', 'OFF' or 'DISABLED'.")
|
||||
// Start an MCP HTTP server connected to this sql-server on the given port
|
||||
ap.SupportsUint(mcpPortFlag, "", "port", "If provided, runs a Dolt MCP HTTP server on this port alongside the sql-server.")
|
||||
// MCP SQL credentials (user required when MCP enabled; password optional)
|
||||
ap.SupportsString(mcpUserFlag, "", "user", "SQL user for MCP to connect as (required when --mcp-port is set).")
|
||||
ap.SupportsString(mcpPasswordFlag, "", "password", "Optional SQL password for MCP to connect with (requires --mcp-user). Defaults to env DOLT_ROOT_PASSWORD if unset.")
|
||||
ap.SupportsString(mcpDatabaseFlag, "", "database", "Optional SQL database name MCP should connect to (requires --mcp-port and --mcp-user).")
|
||||
return ap
|
||||
}
|
||||
|
||||
@@ -266,6 +276,37 @@ func StartServer(ctx context.Context, versionStr, commandStr string, args []stri
|
||||
return err
|
||||
}
|
||||
|
||||
// Optional MCP HTTP port
|
||||
var mcpPortPtr *int
|
||||
if mp, ok := apr.GetInt(mcpPortFlag); ok {
|
||||
mcpPort := mp
|
||||
mcpPortPtr = &mcpPort
|
||||
}
|
||||
|
||||
// Optional MCP SQL user
|
||||
var mcpUserPtr *string
|
||||
if mu, ok := apr.GetValue(mcpUserFlag); ok {
|
||||
user := mu
|
||||
mcpUserPtr = &user
|
||||
}
|
||||
// Optional MCP SQL password
|
||||
var mcpPasswordPtr *string
|
||||
if mpw, ok := apr.GetValue(mcpPasswordFlag); ok {
|
||||
pw := mpw
|
||||
mcpPasswordPtr = &pw
|
||||
}
|
||||
// Optional MCP SQL database
|
||||
var mcpDatabasePtr *string
|
||||
if mdb, ok := apr.GetValue(mcpDatabaseFlag); ok {
|
||||
db := mdb
|
||||
mcpDatabasePtr = &db
|
||||
}
|
||||
|
||||
// Validate and prepare MCP options in dedicated helper
|
||||
if err := validateAndPrepareMCP(serverConfig, mcpPortPtr, mcpUserPtr, mcpPasswordPtr, mcpDatabasePtr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = generateYamlConfigIfNone(ap, help, args, dEnv, serverConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -279,12 +320,24 @@ func StartServer(ctx context.Context, versionStr, commandStr string, args []stri
|
||||
cli.Printf("Starting server with Config %v\n", servercfg.ConfigInfo(serverConfig))
|
||||
|
||||
skipRootUserInitialization := apr.Contains(skipRootUserInitialization)
|
||||
// Build MCP config if any MCP-related options are present
|
||||
var mcpCfg *MCPConfig
|
||||
if mcpPortPtr != nil || (mcpUserPtr != nil && *mcpUserPtr != "") || (mcpPasswordPtr != nil && *mcpPasswordPtr != "") || (mcpDatabasePtr != nil && *mcpDatabasePtr != "") {
|
||||
mcpCfg = &MCPConfig{
|
||||
Port: mcpPortPtr,
|
||||
User: mcpUserPtr,
|
||||
Password: mcpPasswordPtr,
|
||||
Database: mcpDatabasePtr,
|
||||
}
|
||||
}
|
||||
|
||||
startError, closeError := Serve(ctx, &Config{
|
||||
Version: versionStr,
|
||||
ServerConfig: serverConfig,
|
||||
Controller: controller,
|
||||
DoltEnv: dEnv,
|
||||
SkipRootUserInit: skipRootUserInitialization,
|
||||
MCP: mcpCfg,
|
||||
})
|
||||
if startError != nil {
|
||||
return startError
|
||||
|
||||
@@ -17,7 +17,7 @@ require (
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568
|
||||
github.com/go-sql-driver/mysql v1.9.1
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/gocraft/dbr/v2 v2.7.2
|
||||
github.com/golang/snappy v0.0.4
|
||||
github.com/google/uuid v1.6.0
|
||||
@@ -34,7 +34,7 @@ require (
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tealeg/xlsx v1.0.5
|
||||
go.uber.org/zap v1.24.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.40.0
|
||||
golang.org/x/net v0.42.0
|
||||
golang.org/x/sync v0.16.0
|
||||
@@ -58,6 +58,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/creasty/defaults v1.6.0
|
||||
github.com/dolthub/aws-sdk-go-ini-parser v0.0.0-20250305001723-2821c37f6c12
|
||||
github.com/dolthub/dolt-mcp v0.1.2-0.20250825222220-f6a0cfd9ec3e
|
||||
github.com/dolthub/eventsapi_schema v0.0.0-20250725194025-a087efa1ee55
|
||||
github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2
|
||||
github.com/dolthub/go-mysql-server v0.20.1-0.20250825220247-28daf87bfe0e
|
||||
@@ -157,6 +158,7 @@ require (
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||
github.com/lestrrat-go/strftime v1.0.4 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mark3labs/mcp-go v0.34.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.6 // indirect
|
||||
@@ -168,12 +170,14 @@ require (
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/rs/xid v1.4.0 // indirect
|
||||
github.com/sony/gobreaker v0.5.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||
github.com/tklauser/numcpus v0.3.0 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
github.com/zeebo/errs v1.4.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
@@ -182,8 +186,7 @@ require (
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/image v0.18.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
@@ -197,4 +200,6 @@ require (
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
)
|
||||
|
||||
go 1.24.0
|
||||
go 1.24.4
|
||||
|
||||
toolchain go1.24.6
|
||||
|
||||
@@ -165,8 +165,6 @@ github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
|
||||
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/bcicen/jstream v1.0.0 h1:gOi+Sn9mHrpePlENynPKA6Dra/PjLaIpqrTevhfvLAA=
|
||||
github.com/bcicen/jstream v1.0.0/go.mod h1:9ielPxqFry7Y4Tg3j4BfjPocfJ3TbsRtXOAYXYmRuAQ=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -205,6 +203,8 @@ github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waN
|
||||
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dolthub/aws-sdk-go-ini-parser v0.0.0-20250305001723-2821c37f6c12 h1:IdqX7J8vi/Kn3T3Ee0VzqnLqwFmgA2hr8WZETPcQjfM=
|
||||
github.com/dolthub/aws-sdk-go-ini-parser v0.0.0-20250305001723-2821c37f6c12/go.mod h1:rN7X8BHwkjPcfMQQ2QTAq/xM3leUSGLfb+1Js7Y6TVo=
|
||||
github.com/dolthub/dolt-mcp v0.1.2-0.20250825222220-f6a0cfd9ec3e h1:bqMvf19MdCoaIPthBF3auRxHs2C8V0vs9w1xBUm/25c=
|
||||
github.com/dolthub/dolt-mcp v0.1.2-0.20250825222220-f6a0cfd9ec3e/go.mod h1:bw/9fg2/fHI/QnSNL24HaL8+K2ybO/Hoy+ETUWwnZ6A=
|
||||
github.com/dolthub/eventsapi_schema v0.0.0-20250725194025-a087efa1ee55 h1:LdtiPD8IJ+M2XckvfQ/aLG34lET1shz+WMccGBhZRLM=
|
||||
github.com/dolthub/eventsapi_schema v0.0.0-20250725194025-a087efa1ee55/go.mod h1:CoDLfgPqHyBtth0Cp+fi/CmC4R81zJNX4wPjShdZ+Bw=
|
||||
github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww=
|
||||
@@ -253,6 +253,8 @@ github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:r
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ=
|
||||
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
|
||||
@@ -289,8 +291,8 @@ github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhO
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
|
||||
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
@@ -454,6 +456,8 @@ github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lyft/protoc-gen-star v0.5.2/go.mod h1:9toiA3cC7z5uVbODF7kEQ91Xn7XNFkVUl+SrEe+ZORU=
|
||||
github.com/mark3labs/mcp-go v0.34.0 h1:eWy7WBGvhk6EyAAyVzivTCprE52iXJwNtvHV6Cv3bR0=
|
||||
github.com/mark3labs/mcp-go v0.34.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
@@ -566,6 +570,8 @@ github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
|
||||
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -613,6 +619,8 @@ github.com/xitongsys/parquet-go-source v0.0.0-20190524061010-2b72cbee77d5/go.mod
|
||||
github.com/xitongsys/parquet-go-source v0.0.0-20200817004010-026bad9b25d0/go.mod h1:HYhIKsdns7xz80OgkbgJYrtQY7FjHWHKH6cvN7+czGE=
|
||||
github.com/xitongsys/parquet-go-source v0.0.0-20211010230925-397910c5e371 h1:RfGiOP/lWKBeNgpXmCeandYGV4pAnZsl42kX50p1UgE=
|
||||
github.com/xitongsys/parquet-go-source v0.0.0-20211010230925-397910c5e371/go.mod h1:qLb2Itmdcp7KPa5KZKvhE9U1q5bYSOmgeOckF/H2rQA=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -659,14 +667,12 @@ go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFw
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
||||
@@ -51,7 +51,8 @@ dolt_log_in_PST() {
|
||||
}
|
||||
|
||||
setup_no_dolt_init() {
|
||||
export PATH=$PATH:~/go/bin
|
||||
# Ensure locally installed dolt (via `go install`) takes precedence
|
||||
export PATH=~/go/bin:$PATH
|
||||
cd $BATS_TMPDIR
|
||||
|
||||
# remove directory if exists
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env bats
|
||||
load $BATS_TEST_DIRNAME/helper/common.bash
|
||||
load $BATS_TEST_DIRNAME/helper/query-server-common.bash
|
||||
|
||||
make_repo() {
|
||||
mkdir "$1"
|
||||
cd "$1"
|
||||
dolt init
|
||||
cd ..
|
||||
}
|
||||
|
||||
setup() {
|
||||
skiponwindows "tests are flaky on Windows"
|
||||
setup_no_dolt_init
|
||||
make_repo repo1
|
||||
}
|
||||
|
||||
teardown() {
|
||||
stop_sql_server 1 && sleep 0.5
|
||||
rm -rf $BATS_TMPDIR/sql-server-mcp-test$$
|
||||
teardown_common
|
||||
}
|
||||
|
||||
# Helper: wait for an MCP HTTP server to accept TCP connections on the given port
|
||||
# wait_for_mcp_port <PORT> <TIMEOUT_MS>
|
||||
wait_for_mcp_port() {
|
||||
port=$1
|
||||
timeout_ms=$2
|
||||
end_time=$((SECONDS+($timeout_ms/1000)))
|
||||
while [ $SECONDS -lt $end_time ]; do
|
||||
nc -z localhost "$port" >/dev/null 2>&1 && return 0
|
||||
sleep 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
@test "sql-server mcp: --mcp-database connects to specified database" {
|
||||
cd repo1
|
||||
|
||||
# Create target database before starting server
|
||||
dolt sql -q "CREATE DATABASE mcpdb"
|
||||
|
||||
MCP_PORT=$( definePORT )
|
||||
start_sql_server_with_args --host 0.0.0.0 --mcp-port "$MCP_PORT" --mcp-user root --mcp-database mcpdb
|
||||
run wait_for_mcp_port "$MCP_PORT" 8500
|
||||
[ $status -eq 0 ]
|
||||
|
||||
# initialize and list via MCP
|
||||
INIT_FILE=$BATS_TMPDIR/mcp_init_md_$$.json
|
||||
OUT_INIT=$BATS_TMPDIR/mcp_out_init_md_$$.json
|
||||
echo '{"jsonrpc":"2.0","id":"1","method":"initialize","params":{"clientInfo":{"name":"bats","version":"0.0.0"},"capabilities":{}}}' > "$INIT_FILE"
|
||||
run bash -c "curl -sS -D $BATS_TMPDIR/mcp_headers_md_$$.txt -H 'Content-Type: application/json' --data-binary @'$INIT_FILE' http://127.0.0.1:${MCP_PORT}/ > '$OUT_INIT'"
|
||||
[ $status -eq 0 ]
|
||||
SESSION=$(grep -i '^Mcp-Session-Id:' $BATS_TMPDIR/mcp_headers_md_$$.txt | awk -F': ' '{print $2}' | tr -d '\r')
|
||||
[ -n "$SESSION" ]
|
||||
|
||||
CALL_FILE=$BATS_TMPDIR/mcp_call_md_$$.json
|
||||
OUT_CALL=$BATS_TMPDIR/mcp_out_call_md_$$.json
|
||||
echo '{"jsonrpc":"2.0","id":"2","method":"tools/call","params":{"name":"list_databases","arguments":{}}}' > "$CALL_FILE"
|
||||
run bash -c "curl -sS -H 'Content-Type: application/json' -H 'Mcp-Session-Id: '$SESSION --data-binary @'$CALL_FILE' http://127.0.0.1:${MCP_PORT}/ > '$OUT_CALL'"
|
||||
[ $status -eq 0 ]
|
||||
run grep -E "mcpdb" "$OUT_CALL"
|
||||
[ $status -eq 0 ]
|
||||
}
|
||||
|
||||
@test "sql-server mcp: --mcp-user authenticates as specified sql user" {
|
||||
cd repo1
|
||||
|
||||
# TODO: this is currently broken in bats for some reason,
|
||||
# need to figure out why
|
||||
# Create a sql user with no password and read privileges
|
||||
# dolt sql -q "CREATE USER mcpuser@'%'"
|
||||
# dolt sql -q "GRANT ALL PRIVILEGES ON *.* TO mcpuser@'%'"
|
||||
|
||||
MCP_PORT=$( definePORT )
|
||||
start_sql_server_with_args --host 0.0.0.0 --mcp-port "$MCP_PORT" --mcp-user root
|
||||
run wait_for_mcp_port "$MCP_PORT" 8500
|
||||
[ $status -eq 0 ]
|
||||
|
||||
# initialize and list via MCP
|
||||
INIT_FILE=$BATS_TMPDIR/mcp_init_mu_$$.json
|
||||
OUT_INIT=$BATS_TMPDIR/mcp_out_init_mu_$$.json
|
||||
echo '{"jsonrpc":"2.0","id":"1","method":"initialize","params":{"clientInfo":{"name":"bats","version":"0.0.0"},"capabilities":{}}}' > "$INIT_FILE"
|
||||
run bash -c "curl -sS -D $BATS_TMPDIR/mcp_headers_mu_$$.txt -H 'Content-Type: application/json' --data-binary @'$INIT_FILE' http://127.0.0.1:${MCP_PORT}/ > '$OUT_INIT'"
|
||||
[ $status -eq 0 ]
|
||||
SESSION=$(grep -i '^Mcp-Session-Id:' $BATS_TMPDIR/mcp_headers_mu_$$.txt | awk -F': ' '{print $2}' | tr -d '\r')
|
||||
[ -n "$SESSION" ]
|
||||
|
||||
CALL_FILE=$BATS_TMPDIR/mcp_call_mu_$$.json
|
||||
OUT_CALL=$BATS_TMPDIR/mcp_out_call_mu_$$.json
|
||||
echo '{"jsonrpc":"2.0","id":"2","method":"tools/call","params":{"name":"list_databases","arguments":{}}}' > "$CALL_FILE"
|
||||
run bash -c "curl -sS -H 'Content-Type: application/json' -H 'Mcp-Session-Id: '$SESSION --data-binary @'$CALL_FILE' http://127.0.0.1:${MCP_PORT}/ > '$OUT_CALL'"
|
||||
[ $status -eq 0 ]
|
||||
}
|
||||
|
||||
@test "sql-server mcp: starts MCP HTTP server on --mcp-port and serves alongside SQL" {
|
||||
cd repo1
|
||||
|
||||
# Choose distinct ports for SQL and MCP
|
||||
MCP_PORT=$( definePORT )
|
||||
|
||||
# Start the sql-server with MCP enabled
|
||||
start_sql_server_with_args --host 0.0.0.0 --mcp-port="$MCP_PORT" --mcp-user root
|
||||
|
||||
# Verify SQL server accepts connections on $PORT
|
||||
run dolt sql -q "SELECT 1;"
|
||||
[ $status -eq 0 ]
|
||||
|
||||
# Verify MCP HTTP server port opens
|
||||
run wait_for_mcp_port "$MCP_PORT" 8500
|
||||
[ $status -eq 0 ]
|
||||
|
||||
# If wget exists, optionally probe a health endpoint (implementation may add this)
|
||||
if command -v wget >/dev/null 2>&1; then
|
||||
run wget --quiet --tries=1 --spider "http://127.0.0.1:${MCP_PORT}/health"
|
||||
# health endpoint may not exist yet; don't assert success here
|
||||
fi
|
||||
}
|
||||
|
||||
@test "sql-server mcp: without --mcp-port, no MCP port is opened" {
|
||||
cd repo1
|
||||
|
||||
# Pick a free port and ensure server doesn't bind it as MCP when not requested
|
||||
MCP_PORT=$( definePORT )
|
||||
|
||||
# Start the sql-server WITHOUT MCP
|
||||
start_sql_server
|
||||
|
||||
# Verify SQL server accepts connections on $PORT
|
||||
run dolt sql -q "SELECT 1;"
|
||||
[ $status -eq 0 ]
|
||||
|
||||
# Verify MCP_PORT is not in use (best-effort; we chose an unused port)
|
||||
run nc -z localhost "$MCP_PORT"
|
||||
[ $status -ne 0 ]
|
||||
}
|
||||
|
||||
@test "sql-server mcp: HTTP initialize and call list_databases tool" {
|
||||
cd repo1
|
||||
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
skip "curl not available"
|
||||
fi
|
||||
|
||||
MCP_PORT=$( definePORT )
|
||||
|
||||
# Start server with MCP enabled
|
||||
start_sql_server_with_args --host 0.0.0.0 --mcp-port="$MCP_PORT" --mcp-user root
|
||||
|
||||
# Wait for MCP port to accept connections
|
||||
run wait_for_mcp_port "$MCP_PORT" 8500
|
||||
[ $status -eq 0 ]
|
||||
|
||||
# Initialize session
|
||||
INIT_FILE=$BATS_TMPDIR/mcp_init_$$.json
|
||||
OUT_INIT=$BATS_TMPDIR/mcp_out_init_$$.json
|
||||
cat > "$INIT_FILE" <<'EOF'
|
||||
{"jsonrpc":"2.0","id":"1","method":"initialize","params":{"clientInfo":{"name":"bats","version":"0.0.0"},"capabilities":{}}}
|
||||
EOF
|
||||
run bash -c "curl -sS -D $BATS_TMPDIR/mcp_headers_$$.txt -H 'Content-Type: application/json' --data-binary @'$INIT_FILE' http://127.0.0.1:${MCP_PORT}/ > '$OUT_INIT'"
|
||||
[ $status -eq 0 ]
|
||||
|
||||
# Extract session id from response headers
|
||||
SESSION=$(grep -i '^Mcp-Session-Id:' $BATS_TMPDIR/mcp_headers_$$.txt | awk -F': ' '{print $2}' | tr -d '\r')
|
||||
[ -n "$SESSION" ]
|
||||
|
||||
# Call list_databases with session header
|
||||
CALL_FILE=$BATS_TMPDIR/mcp_call_$$.json
|
||||
OUT_CALL=$BATS_TMPDIR/mcp_out_call_$$.json
|
||||
cat > "$CALL_FILE" <<'EOF'
|
||||
{"jsonrpc":"2.0","id":"2","method":"tools/call","params":{"name":"list_databases","arguments":{}}}
|
||||
EOF
|
||||
run bash -c "curl -sS -H 'Content-Type: application/json' -H 'Mcp-Session-Id: '$SESSION --data-binary @'$CALL_FILE' http://127.0.0.1:${MCP_PORT}/ > '$OUT_CALL'"
|
||||
[ $status -eq 0 ]
|
||||
|
||||
# Expect known databases in response payload (markdown or text content)
|
||||
run grep -E "information_schema|mysql|repo1" "$OUT_CALL"
|
||||
[ $status -eq 0 ]
|
||||
}
|
||||
|
||||
@test "sql-server mcp: invalid --mcp-port values (0 and >65535)" {
|
||||
cd repo1
|
||||
|
||||
# mcp-port = 0
|
||||
SQL_PORT=$( definePORT )
|
||||
run dolt sql-server --host 0.0.0.0 --port="$SQL_PORT" --socket "dolt.$SQL_PORT.sock" --mcp-port 0 --mcp-user root
|
||||
[ $status -ne 0 ]
|
||||
|
||||
# mcp-port > 65535
|
||||
SQL_PORT=$( definePORT )
|
||||
run dolt sql-server --host 0.0.0.0 --port="$SQL_PORT" --socket "dolt.$SQL_PORT.sock" --mcp-port 70000 --mcp-user root
|
||||
[ $status -ne 0 ]
|
||||
}
|
||||
|
||||
@test "sql-server mcp: --mcp-port identical to --port fails startup" {
|
||||
cd repo1
|
||||
PORT=$( definePORT )
|
||||
run dolt sql-server --host 0.0.0.0 --port="$PORT" --socket "dolt.$PORT.sock" --mcp-port "$PORT" --mcp-user root
|
||||
[ $status -ne 0 ]
|
||||
}
|
||||
|
||||
@test "sql-server mcp: multiple --mcp-port flags result in error" {
|
||||
cd repo1
|
||||
SQL_PORT=$( definePORT )
|
||||
# Provide duplicate mcp-port flags; expect failure
|
||||
run dolt sql-server --host 0.0.0.0 --port="$SQL_PORT" --socket "dolt.$SQL_PORT.sock" --mcp-port 45001 --mcp-user root --mcp-port 45002 --mcp-user root
|
||||
[ $status -ne 0 ]
|
||||
}
|
||||
|
||||
@test "sql-server mcp: any MCP arg without --mcp-port errors" {
|
||||
cd repo1
|
||||
SQL_PORT=$( definePORT )
|
||||
# --mcp-user without --mcp-port should fail
|
||||
run dolt sql-server --host 0.0.0.0 --port="$SQL_PORT" --socket "dolt.$SQL_PORT.sock" --mcp-user root
|
||||
[ $status -ne 0 ]
|
||||
# --mcp-password without --mcp-port should fail
|
||||
run dolt sql-server --host 0.0.0.0 --port="$SQL_PORT" --socket "dolt.$SQL_PORT.sock" --mcp-password secret
|
||||
[ $status -ne 0 ]
|
||||
# --mcp-database without --mcp-port should fail
|
||||
run dolt sql-server --host 0.0.0.0 --port="$SQL_PORT" --socket "dolt.$SQL_PORT.sock" --mcp-database somedb
|
||||
[ $status -ne 0 ]
|
||||
}
|
||||
|
||||
@test "sql-server mcp: restart with same --mcp-port succeeds after stop" {
|
||||
cd repo1
|
||||
MCP_PORT=$( definePORT )
|
||||
start_sql_server_with_args --host 0.0.0.0 --mcp-port "$MCP_PORT" --mcp-user root
|
||||
run wait_for_mcp_port "$MCP_PORT" 8500
|
||||
[ $status -eq 0 ]
|
||||
stop_sql_server 1
|
||||
|
||||
# Immediate restart on same MCP port
|
||||
start_sql_server_with_args --host 0.0.0.0 --mcp-port "$MCP_PORT" --mcp-user root
|
||||
run wait_for_mcp_port "$MCP_PORT" 8500
|
||||
[ $status -eq 0 ]
|
||||
|
||||
# initialize and list
|
||||
INIT_FILE=$BATS_TMPDIR/mcp_init_rs_$$.json
|
||||
OUT_INIT=$BATS_TMPDIR/mcp_out_init_rs_$$.json
|
||||
echo '{"jsonrpc":"2.0","id":"1","method":"initialize","params":{"clientInfo":{"name":"bats","version":"0.0.0"},"capabilities":{}}}' > "$INIT_FILE"
|
||||
run bash -c "curl -sS -D $BATS_TMPDIR/mcp_headers_rs_$$.txt -H 'Content-Type: application/json' --data-binary @'$INIT_FILE' http://127.0.0.1:${MCP_PORT}/ > '$OUT_INIT'"
|
||||
[ $status -eq 0 ]
|
||||
SESSION=$(grep -i '^Mcp-Session-Id:' $BATS_TMPDIR/mcp_headers_rs_$$.txt | awk -F': ' '{print $2}' | tr -d '\r')
|
||||
[ -n "$SESSION" ]
|
||||
CALL_FILE=$BATS_TMPDIR/mcp_call_rs_$$.json
|
||||
OUT_CALL=$BATS_TMPDIR/mcp_out_call_rs_$$.json
|
||||
echo '{"jsonrpc":"2.0","id":"2","method":"tools/call","params":{"name":"list_databases","arguments":{}}}' > "$CALL_FILE"
|
||||
run bash -c "curl -sS -H 'Content-Type: application/json' -H 'Mcp-Session-Id: '$SESSION --data-binary @'$CALL_FILE' http://127.0.0.1:${MCP_PORT}/ > '$OUT_CALL'"
|
||||
[ $status -eq 0 ]
|
||||
run grep -E "information_schema|mysql|repo1" "$OUT_CALL"
|
||||
[ $status -eq 0 ]
|
||||
}
|
||||
|
||||
@test "sql-server mcp: forceful termination closes servers cleanly; restart works" {
|
||||
cd repo1
|
||||
MCP_PORT=$( definePORT )
|
||||
start_sql_server_with_args --host 0.0.0.0 --mcp-port "$MCP_PORT" --mcp-user root
|
||||
run wait_for_mcp_port "$MCP_PORT" 8500
|
||||
[ $status -eq 0 ]
|
||||
|
||||
# Force kill the server
|
||||
kill -9 $SERVER_PID
|
||||
# Give OS a moment to release ports
|
||||
sleep 1
|
||||
|
||||
# Restart with same MCP port
|
||||
start_sql_server_with_args --host 0.0.0.0 --mcp-port "$MCP_PORT" --mcp-user root
|
||||
run wait_for_mcp_port "$MCP_PORT" 8500
|
||||
[ $status -eq 0 ]
|
||||
|
||||
# initialize and list
|
||||
INIT_FILE=$BATS_TMPDIR/mcp_init_fk_$$.json
|
||||
OUT_INIT=$BATS_TMPDIR/mcp_out_init_fk_$$.json
|
||||
echo '{"jsonrpc":"2.0","id":"1","method":"initialize","params":{"clientInfo":{"name":"bats","version":"0.0.0"},"capabilities":{}}}' > "$INIT_FILE"
|
||||
run bash -c "curl -sS -D $BATS_TMPDIR/mcp_headers_fk_$$.txt -H 'Content-Type: application/json' --data-binary @'$INIT_FILE' http://127.0.0.1:${MCP_PORT}/ > '$OUT_INIT'"
|
||||
[ $status -eq 0 ]
|
||||
SESSION=$(grep -i '^Mcp-Session-Id:' $BATS_TMPDIR/mcp_headers_fk_$$.txt | awk -F': ' '{print $2}' | tr -d '\r')
|
||||
[ -n "$SESSION" ]
|
||||
CALL_FILE=$BATS_TMPDIR/mcp_call_fk_$$.json
|
||||
OUT_CALL=$BATS_TMPDIR/mcp_out_call_fk_$$.json
|
||||
echo '{"jsonrpc":"2.0","id":"2","method":"tools/call","params":{"name":"list_databases","arguments":{}}}' > "$CALL_FILE"
|
||||
run bash -c "curl -sS -H 'Content-Type: application/json' -H 'Mcp-Session-Id: '$SESSION --data-binary @'$CALL_FILE' http://127.0.0.1:${MCP_PORT}/ > '$OUT_CALL'"
|
||||
[ $status -eq 0 ]
|
||||
run grep -E "information_schema|mysql|repo1" "$OUT_CALL"
|
||||
[ $status -eq 0 ]
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
module github.com/dolthub/dolt/integration-tests/go-sql-server-driver
|
||||
|
||||
go 1.24.0
|
||||
go 1.24.4
|
||||
|
||||
toolchain go1.24.6
|
||||
|
||||
require (
|
||||
github.com/dolthub/dolt/go v0.40.4
|
||||
github.com/go-sql-driver/mysql v1.9.1
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/sync v0.16.0
|
||||
|
||||
@@ -4,8 +4,8 @@ github.com/creasty/defaults v1.6.0 h1:ltuE9cfphUtlrBeomuu8PEyISTXnxqkBIoQfXgv7BS
|
||||
github.com/creasty/defaults v1.6.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
|
||||
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
|
||||
Reference in New Issue
Block a user