Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ GATEWAY_DB_PASSWORD=
GATEWAY_DB_NAME=./data/mcp-gateway.db
GATEWAY_DB_SSL_MODE=disable
GATEWAY_STORAGE_DISK_PATH=
GATEWAY_STORAGE_API_URL=
GATEWAY_STORAGE_API_CONFIG_JSON_PATH=
GATEWAY_STORAGE_API_TIMEOUT=5

# Notifier Configuration
APISERVER_NOTIFIER_ROLE=sender
Expand Down
4 changes: 4 additions & 0 deletions configs/mcp-gateway.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ storage:
# Disk configuration (only used when type is disk)
disk:
path: "${GATEWAY_STORAGE_DISK_PATH:}"
api:
url: "${GATEWAY_STORAGE_API_URL:}"
configJSONPath: "${GATEWAY_STORAGE_API_CONFIG_JSON_PATH:}"
timeout: "${GATEWAY_STORAGE_API_TIMEOUT:}"

# Notifier configuration
notifier:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ require (
github.com/spf13/pflag v1.0.6 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSW
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
Expand Down
7 changes: 7 additions & 0 deletions internal/common/config/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ type (
Type string `yaml:"type"` // disk or db
Database DatabaseConfig `yaml:"database"` // database configuration for db type
Disk DiskStorageConfig `yaml:"disk"` // disk configuration for disk type
API APIStorageConfig `yaml:"api"` // disk configuration for api type
}

DiskStorageConfig struct {
Path string `yaml:"path"` // path for disk storage
}

APIStorageConfig struct {
Url string `yaml:"url"` // http url for api
ConfigJSONPath string `yaml:"configJSONPath"` // configJSONPath for config in http response
Timeout int `yaml:"timeout"` // timeout(seconds) for http request
}
)
122 changes: 122 additions & 0 deletions internal/mcp/storage/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package storage

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"

"github.com/tidwall/gjson"

"github.com/mcp-ecosystem/mcp-gateway/internal/common/config"

"go.uber.org/zap"
)

// APIStore implements the Store interface using the remote http server
type APIStore struct {
logger *zap.Logger
url string
// read config from response(json body) using gjson
configJSONPath string
timeout int
}

var _ Store = (*APIStore)(nil)

// NewAPIStore creates a new api-based store
func NewAPIStore(logger *zap.Logger, url string, configJSONPath string, timeout int) (*APIStore, error) {
logger = logger.Named("mcp.store")

logger.Info("Using configuration url", zap.String("path", url))

return &APIStore{
logger: logger,
url: url,
configJSONPath: configJSONPath,
timeout: timeout,
}, nil
}

// Create implements Store.Create
func (s *APIStore) Create(_ context.Context, server *config.MCPConfig) error {
// only use for read config
return nil
Comment on lines +45 to +46
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Store methods for Create, Update, and Delete are no-ops.

Consider returning an explicit error (e.g., ErrNotSupported) to clearly indicate these operations aren’t supported.

}

// Get implements Store.Get
func (s *APIStore) Get(_ context.Context, name string) (*config.MCPConfig, error) {
jsonStr, err := s.request()
if err != nil {
return nil, err
}
var config config.MCPConfig
err = json.Unmarshal([]byte(jsonStr), &config)
if err != nil {
return nil, err
}
return &config, nil
}

// List implements Store.List
func (s *APIStore) List(_ context.Context) ([]*config.MCPConfig, error) {
jsonStr, err := s.request()
if err != nil {
return nil, err
}
var configs []*config.MCPConfig
err = json.Unmarshal([]byte(jsonStr), &configs)
if err != nil {
return nil, err
}
return configs, nil
}

// Update implements Store.Update
func (s *APIStore) Update(_ context.Context, server *config.MCPConfig) error {
// only use for read config
return nil
}

// Delete implements Store.Delete
func (s *APIStore) Delete(_ context.Context, name string) error {
// only use for read config
return nil
}

func (s *APIStore) request() (string, error) {
client := &http.Client{
Timeout: time.Duration(s.timeout) * time.Second,
}
resp, err := client.Get(s.url)
if err != nil {
s.logger.Error("failed to request url",
zap.String("url", s.url),
zap.Error(err))
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
s.logger.Error("failed to read response",
zap.String("url", s.url),
zap.Error(err))
return "", err
}
jsonString := string(body)
s.logger.Debug("read storage api response", zap.String("body", jsonString))
if s.configJSONPath == "" {
return jsonString, nil
}
result := gjson.Get(jsonString, s.configJSONPath)
if !result.Exists() {
Comment thread
qiankunli marked this conversation as resolved.
err = fmt.Errorf("configJSONPath is not in response: %s", s.configJSONPath)
s.logger.Error("configJSONPath is not in response",
zap.String("url", s.url),
zap.Error(err))
return "", err
}
return result.Raw, nil
}
5 changes: 4 additions & 1 deletion internal/mcp/storage/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package storage
import (
"fmt"

"github.com/mcp-ecosystem/mcp-gateway/internal/common/config"
"go.uber.org/zap"

"github.com/mcp-ecosystem/mcp-gateway/internal/common/config"
)

// NewStore creates a new store based on configuration
Expand All @@ -19,6 +20,8 @@ func NewStore(logger *zap.Logger, cfg *config.StorageConfig) (Store, error) {
return nil, err
}
return NewDBStore(logger, DatabaseType(cfg.Database.Type), dsn)
case "api":
return NewAPIStore(logger, cfg.API.Url, cfg.API.ConfigJSONPath, cfg.API.Timeout)
default:
return nil, fmt.Errorf("unsupported storage type: %s", cfg.Type)
}
Expand Down