Scaled MCP Server について
Scaled MCP Server は水平スケーリングに対応した Go ライブラリで、MCP 2025-03 仕様を実装し、ロードバランサー配下でのデプロイをサポートします。Redis によるセッション管理を備えたMCP サーバーを自社アプリケーションに組み込みたい Go 開発者に向いています。
主な機能
- 01/mcp エンドポイントとオプションの SSE による HTTP トランスポート
- 02Redis またはインメモリによるセッション管理
- 03Actor ベースのセッションとメッセージルーティング
- 04複数ノードでのロードバランス構成
- 05構造体タグによるツール定義
活用シーン
- 01複数ノードにまたがる MCP サーバーの運用
- 02既存の Go アプリケーションのルーターに MCP サーバーを組み込む
MCP 対応
MCP 2025-03 仕様を実装し、HTTP エンドポイント経由で MCP クライアントからツールなどを利用できるサーバーを構築します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
Scaled MCP Server
ロードバランスされたデプロイメントに対応する、水平スケーリング可能な MCP(Message Context Protocol)サーバーの実装です。
概要
Scaled MCP Server は、水平スケーリングに対応した MCP 2025-03 仕様を実装する Go ライブラリです。アプリケーションへの組み込みを想定して設計されており、柔軟な設定オプションを提供します。
機能
- HTTP トランスポート:メインの
/mcpエンドポイント、オプションの SSE エンドポイント、機能ネゴシエーションを備えた柔軟な HTTP トランスポート - セッション管理:Redis またはインメモリのオプションによる分散セッション管理
- アクターシステム:セッション管理とメッセージルーティングにアクターベースのアーキテクチャを使用
- 水平スケーリング:複数のノードにまたがるロードバランスされたデプロイメントに対応
インストール
go get github.com/traego/scaled-mcp@latest
注: このライブラリには Go 1.24 以降が必要です。
使用方法
静的ツールを使った基本的なサーバー
package main
import (
"context"
"fmt"
"log/slog"
"os"
"os/signal"
"syscall"
"github.com/traego/scaled-mcp/pkg/config"
"github.com/traego/scaled-mcp/pkg/resources"
"github.com/traego/scaled-mcp/pkg/server"
)
func main() {
// Configure logging
logHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})
slog.SetDefault(slog.New(logHandler))
// Create a server with default configuration
cfg := config.DefaultConfig()
// Use in-memory session store for simplicity
cfg.Session.UseInMemory = true
// Create a static tool registry
registry := resources.NewStaticToolRegistry()
// Define and register a simple calculator tool
calculatorTool := resources.NewTool("calculator").
WithDescription("Performs basic arithmetic operations").
WithInputs([]resources.ToolInput{
{
Name: "operation",
Type: "string",
Description: "Operation to perform (add, subtract, multiply, divide)",
Required: true,
},
{
Name: "a",
Type: "number",
Description: "First operand",
Required: true,
},
{
Name: "b",
Type: "number",
Description: "Second operand",
Required: true,
},
}).
Build()
// Register the tool with the registry
registry.RegisterTool(calculatorTool)
// Define a prompt for the server
prompt := "You are a helpful AI assistant that can perform calculations using the calculator tool."
// Create the server with the tool registry and prompt
srv, err := server.NewMcpServer(cfg,
server.WithToolRegistry(registry),
server.WithServerInfo("Example MCP Server", "1.0.0"),
server.WithPrompt(prompt),
)
if err != nil {
slog.Error("Failed to create server", "error", err)
os.Exit(1)
}
// Set up the tool handler
registry.SetToolHandler("calculator", func(ctx context.Context, params map[string]interface{}) (interface{}, error) {
// Extract parameters
operation, ok := params["operation"].(string)
if !ok {
return nil, fmt.Errorf("%w: operation must be a string", resources.ErrInvalidParams)
}
a, ok := params["a"].(float64)
if !ok {
return nil, fmt.Errorf("%w: a must be a number", resources.ErrInvalidParams)
}
b, ok := params["b"].(float64)
if !ok {
return nil, fmt.Errorf("%w: b must be a number", resources.ErrInvalidParams)
}
// Perform the calculation
var result float64
switch operation {
case "add":
result = a + b
case "subtract":
result = a - b
case "multiply":
result = a * b
case "divide":
if b == 0 {
return nil, fmt.Errorf("%w: division by zero", resources.ErrInvalidParams)
}
result = a / b
default:
return nil, fmt.Errorf("%w: unknown operation %s", resources.ErrInvalidParams, operation)
}
return map[string]interface{}{
"result": result,
}, nil
})
// Start the server in a goroutine
go func() {
if err := srv.Start(context.Background()); err != nil {
slog.Error("Failed to start server", "error", err)
os.Exit(1)
}
}()
slog.Info("Server started", "host", cfg.HTTP.Host, "port", cfg.HTTP.Port)
// Wait for termination signal
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<-sig
// Shutdown the server
slog.Info("Shutting down server...")
shutdownCtx, cancel := context.WithTimeout(context.Background(), cfg.HTTP.ShutdownTimeout)
defer cancel()
if err := srv.Stop(shutdownCtx); err != nil {
slog.Error("Failed to stop server", "error", err)
}
slog.Info("Server stopped")
}
### Using an External HTTP Server
You can use your own HTTP server with the MCP transport:
```go
package main
import (
"context"
"log"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/traego/scaled-mcp/pkg/config"
"github.com/traego/scaled-mcp/pkg/server"
"github.com/traego/scaled-mcp/pkg/transport"
)
func main() {
// Create a server with default configuration
cfg := config.DefaultConfig()
cfg.Session.UseInMemory = true
// Create the MCP server but don't start the HTTP server
srv, err := server.NewServer(cfg)
if err != nil {
log.Fatalf("Failed to create server: %v", err)
}
// Create a custom router
r := chi.NewRouter()
// Add middleware
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
// Add CORS middleware - important when using an external server
r.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300,
}))
// Add your custom routes
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Welcome to the MCP server!"))
})
// Create the HTTP transport with the custom router
httpTransport := transport.NewHTTPTransport(
cfg,
srv.GetActorSystem(),
srv.GetSessionManager(),
transport.WithExternalRouter(r),
)
// Start the MCP server without HTTP
if err := srv.Start(context.Background()); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
// Start the HTTP transport
if err := httpTransport.Start(); err != nil {
log.Fatalf("Failed to start HTTP transport: %v", err)
}
// Start your HTTP server
log.Printf("Starting HTTP server on %s:%d", cfg.HTTP.Host, cfg.HTTP.Port)
if err := http.ListenAndServe(":8080", r); err != nil {
log.Fatalf("HTTP server error: %v", err)
}
}
## Dynamic Tool Registry Example
This library supports both static and dynamic tool registries. Here's an client_example of using a dynamic tool registry:
```go
// Create a custom tool provider
toolProvider := NewExampleToolProvider()
// Create a dynamic tool registry with the provider
registry := resources.NewDynamicToolRegistry(toolProvider)
// Create server with the dynamic tool registry
cfg := config.DefaultConfig()
mcpServer, err := server.NewMcpServer(cfg,
server.WithToolRegistry(registry),
)
ツールの定義
このライブラリでは、ツールの入力を定義する方法が 3 つあります。
1. 構造体ベースのリフレクションを使う(推奨)
// Define input struct with mcp tags
type CalculatorInput struct {
Operation string `mcp:"operation,The operation to perform (add subtract multiply divide),required"`
A float64 `mcp:"a,First operand,required"`
B float64 `mcp:"b,Second operand,required"`
}
// Type-safe handler function
func calculatorHandler(ctx context.Context, input *CalculatorInput) (interface{}, error) {
// Direct access to typed fields: input.Operation, input.A, input.B
switch input.Operation {
case "add":
return map[string]interface{}{"result": input.A + input.B}, nil
// ...
}
}
// Register with automatic schema generation (method 1)
calculatorType := reflect.TypeOf(CalculatorInput{})
registry.RegisterStructToolWithHandler("calculator", "Performs arithmetic operations", calculatorType, calculatorHandler)
// Or use the convenience function (method 2)
resources.RegisterStructTool(registry, "calculator", "Performs arithmetic operations", calculatorHandler)
### Generics-Based Tool Registration (New in June 2025)
For even better type safety, you can use the new generics-based approach with two type parameters for input and output:
```go
type CalculatorInput struct {
Operation string `mcp:"operation,The operation to perform,required"`
A float64 `mcp:"a,First operand,required"`
B float64 `mcp:"b,Second operand,required"`
}
type CalculatorOutput struct {
Result float64 `mcp:"result,The calculation result,required"`
Operation string `mcp:"operation,The operation performed,required"`
}
func calculatorHandler(ctx context.Context, input *CalculatorInput) (*CalculatorOutput, error) {
result := input.A + input.B
return &CalculatorOutput{
Result: result,
Operation: input.Operation,
}, nil
}
// Register with compile-time type safety for both input and output
err := resources.RegisterStructToolWithTypes(registry, "calculator", "Performs arithmetic", calculatorHandler)
この方法には、次の利点があります。
- 入力と出力の両方でコンパイル時の型安全性を確保
- 入力型と出力型の両方のスキーマを自動生成
- 登録時のリフレクションが不要(代わりにジェネリクスを使用)
- 完全な型チェックによる IDE サポートの向上
#### 2. Using WithInputs
```go
weatherTool := resources.NewTool("weather").
WithDescription("Get weather information for a location").
WithInputs([]resources.ToolInput{
{
Name: "location",
Type: "string",
Description: "The location to get weather for",
Required: true,
},
{
Name: "units",
Type: "string",
Description: "Temperature units (celsius or fahrenheit)",
Default: "celsius",
},
}).
Build()
3. 個別のパラメータメソッドを使う
calculatorTool := resources.NewTool("calculator").
WithDescription("Performs basic arithmetic operations").
WithString("operation").
Required().
Description("Operation to perform (add, subtract, multiply, divide)").
Add().
WithNumber("a").
Required().
Description("First operand").
Add().
WithNumber("b").
Required().
Description("Second operand").
Add().
Build()
構造体タグの形式
mcp 構造体タグの形式は、mcp:"name,description,required,default=value" です。
- name:スキーマ内のフィールド名(デフォルトではフィールド名を小文字にしたもの)
- description:フィールドの人間が読める説明
- required:フィールドを必須として指定
- default=value:オプションのフィールドにデフォルト値を設定
- -:フィールドをスキップ(スキーマに含めない)
対応する Go の型:
string→"string"int、int64など →"integer"float64、float32→"number"bool→"boolean"[]T、[N]T→"array"struct、map→"object"*T→Tと同じ(ポインター型)
重要な注意事項
CORS の設定
MCP トランスポートで外部 HTTP サーバーを使用する場合は、ルーターで CORS 設定を行う必要があります。上記の例のように外部ルーターを使用するとき、MCP トランスポートは CORS 設定を適用しません。
セッション管理
本番環境へのデプロイメントでは、水平スケーリングに対応するため、セッション管理に Redis を使用することを推奨します。インメモリのセッションストアは、開発またはテストでのみ使用してください。
今後の予定
- 認可の例と認証コンテキストの引き継ぎ
- メトリクスエンドポイント(prometheus)。アクターの開始/停止、平均セッション時間などを対象とします
- セッションアクターフック
- MCP 仕様
- リスト変更通知
- サンプリング
- ルート
- 補完
- ロギング
- A2A 仕様
- 詳細はこちらに追加しました https://github.com/Traego/scaled-mcp/wiki/A2A-Support
- プラグインをサポートするパイプライン処理
- K8S クラスタリングの接続とテスト
- 検索のサポート(https://github.com/modelcontextprotocol/modelcontextprotocol/pull/322)
- すべての機能(リソース、プロンプトなど)の全面的なテスト
- テストカバレッジを 80% にする
- 新しいサーバー API を少し整理して、起動しやすくする
- より適切なデフォルト設定
- goakt の vNext に移行し、カスタムのメッセージングスケジュールを置き換える(バグ回避策として使用していました)
貢献
貢献を歓迎します。お気軽にプルリクエストを送信してください。
- リポジトリをフォークします
- フィーチャーブランチを作成します(
git checkout -b feature/amazing-feature) - 変更をコミットします(
git commit -m 'Add some amazing feature') - ブランチにプッシュします(
git push origin feature/amazing-feature) - プルリクエストを作成します
開発
テスト
テストを実行します:
go test ./...
カバレッジ付きでテストを実行します:
go test -race -coverprofile=coverage.txt -covermode=atomic ./...
ブラウザーでカバレッジレポートを表示します:
go tool cover -html=coverage.txt
コードカバレッジ
このプロジェクトでは、コードカバレッジのレポートに Codecov を使用しています。カバレッジレポートは CI の実行時に自動的に生成され、アップロードされます。
カバレッジダッシュボードを表示するには、codecov.io/gh/traego/scaled-mcp にアクセスしてください。
ライセンス
このプロジェクトは MIT License の下でライセンスされています。
ドキュメント
API の詳細なドキュメントについては、GoDoc を参照してください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。