Skip to content

Commit 0faba8e

Browse files
committed
add proxy info
1 parent 7fd7a50 commit 0faba8e

File tree

11 files changed

+223
-58
lines changed

11 files changed

+223
-58
lines changed

.env.example.toml

+5-5
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ port = 8025
44
[api_server]
55
port = 8027
66

7-
[mcp_server_commands]
8-
puppeteer = "npx -y @modelcontextprotocol/server-puppeteer"
9-
fetch = "uvx mcp-server-fetch"
10-
time = "docker run -i --rm mcp/time"
7+
[mcp_servers]
8+
puppeteer = { command="npx -y @modelcontextprotocol/server-puppeteer", share_process=true }
9+
fetch = { command="uvx mcp-server-fetch", share_process=true }
10+
time = { command="docker run -i --rm mcp/time", share_process=true }
1111

1212
[remote_apis]
13-
get_server_command = "https://mcp.so/api/get-server-command"
13+
get_server_config = "http://127.0.0.1:3000/api/get-server-config"

handler/proxy/messages.go

+52-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package proxy
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"net/http"
7+
"time"
68

79
"github.com/chatmcp/mcprouter/service/jsonrpc"
810
"github.com/chatmcp/mcprouter/service/mcpclient"
@@ -32,12 +34,37 @@ func Messages(c echo.Context) error {
3234
return ctx.JSONRPCError(jsonrpc.ErrorParseError, nil)
3335
}
3436

37+
proxyInfo := session.ProxyInfo()
3538
sseKey := session.Key()
3639

40+
proxyInfo.JSONRPCVersion = request.JSONRPC
41+
proxyInfo.RequestMethod = request.Method
42+
proxyInfo.RequestTime = time.Now()
43+
proxyInfo.RequestParams = request.Params
44+
45+
if request.ID != nil {
46+
proxyInfo.RequestID = request.ID
47+
}
48+
49+
if request.Method == "initialize" {
50+
paramsB, _ := json.Marshal(request.Params)
51+
params := &jsonrpc.InitializeParams{}
52+
if err := json.Unmarshal(paramsB, params); err != nil {
53+
return ctx.JSONRPCError(jsonrpc.ErrorParseError, nil)
54+
}
55+
56+
proxyInfo.ClientName = params.ClientInfo.Name
57+
proxyInfo.ClientVersion = params.ClientInfo.Version
58+
proxyInfo.ProtocolVersion = params.ProtocolVersion
59+
60+
session.SetProxyInfo(proxyInfo)
61+
ctx.StoreSession(sessionID, session)
62+
}
63+
3764
client := ctx.GetClient(sseKey)
3865

3966
if client == nil {
40-
command := session.Command()
67+
command := proxyInfo.ServerCommand
4168
_client, err := mcpclient.NewStdioClient(command)
4269
if err != nil {
4370
fmt.Printf("connect to mcp server failed: %v\n", err)
@@ -50,7 +77,6 @@ func Messages(c echo.Context) error {
5077
}
5178

5279
ctx.StoreClient(sseKey, _client)
53-
ctx.StoreSession(sessionID, session)
5480

5581
client = _client
5682

@@ -67,13 +93,36 @@ func Messages(c echo.Context) error {
6793
response, err := client.ForwardMessage(request)
6894
if err != nil {
6995
fmt.Printf("forward message failed: %v\n", err)
96+
session.Close()
97+
ctx.DeleteClient(sseKey)
7098
return ctx.JSONRPCError(jsonrpc.ErrorProxyError, request.ID)
7199
}
72100

73101
if response != nil {
102+
if request.Method == "initialize" && response.Result != nil {
103+
resultB, _ := json.Marshal(response.Result)
104+
result := &jsonrpc.InitializeResult{}
105+
if err := json.Unmarshal(resultB, result); err != nil {
106+
fmt.Printf("unmarshal initialize result failed: %v\n", err)
107+
return ctx.JSONRPCError(jsonrpc.ErrorParseError, request.ID)
108+
}
109+
110+
proxyInfo.ServerName = result.ServerInfo.Name
111+
proxyInfo.ServerVersion = result.ServerInfo.Version
112+
113+
session.SetProxyInfo(proxyInfo)
114+
ctx.StoreSession(sessionID, session)
115+
}
116+
117+
// not notification message, send sse message
74118
session.SendMessage(response.String())
75119
}
76120

77-
// notification message
121+
proxyInfo.ResponseTime = time.Now()
122+
proxyInfo.ResponseDuration = time.Since(proxyInfo.RequestTime)
123+
proxyInfoB, _ := json.Marshal(proxyInfo)
124+
125+
fmt.Printf("proxyInfo: %s, cost: %f\n", string(proxyInfoB), proxyInfo.ResponseDuration.Seconds())
126+
78127
return ctx.JSONRPCResponse(response)
79128
}

handler/proxy/sse.go

+23-10
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77

88
"github.com/chatmcp/mcprouter/service/mcpserver"
99
"github.com/chatmcp/mcprouter/service/proxy"
10-
"github.com/google/uuid"
10+
"github.com/chatmcp/mcprouter/util"
1111
"github.com/labstack/echo/v4"
1212
)
1313

@@ -25,19 +25,32 @@ func SSE(c echo.Context) error {
2525
return c.String(http.StatusBadRequest, "Key is required")
2626
}
2727

28-
command := mcpserver.GetCommand(key)
29-
if command == "" {
30-
return c.String(http.StatusBadRequest, "Server command not found")
28+
serverConfig := mcpserver.GetServerConfig(key)
29+
if serverConfig == nil {
30+
return c.String(http.StatusBadRequest, "Invalid server config")
3131
}
3232

3333
writer, err := proxy.NewSSEWriter(c)
3434
if err != nil {
3535
return c.String(http.StatusInternalServerError, err.Error())
3636
}
3737

38+
// sessionID := uuid.New().String()
39+
sessionID := util.MD5(key)
40+
41+
proxyInfo := &proxy.ProxyInfo{
42+
ServerKey: key,
43+
SSERequestTime: time.Now(),
44+
SessionID: sessionID,
45+
ServerUUID: serverConfig.ServerUUID,
46+
ServerConfigName: serverConfig.ServerName,
47+
ServerShareProcess: serverConfig.ShareProcess,
48+
ServerCommand: serverConfig.Command,
49+
ServerCommandHash: serverConfig.CommandHash,
50+
}
51+
3852
// store session
39-
sessionID := uuid.New().String()
40-
session := proxy.NewSSESession(writer, key, command)
53+
session := proxy.NewSSESession(writer, proxyInfo)
4154
ctx.StoreSession(sessionID, session)
4255
defer ctx.DeleteSession(sessionID)
4356

@@ -68,10 +81,10 @@ func SSE(c echo.Context) error {
6881
return
6982
case <-heartbeatTicker.C:
7083
// Send heartbeat comment
71-
if err := writer.SendHeartbeat(); err != nil {
72-
session.Close()
73-
return
74-
}
84+
// if err := writer.SendHeartbeat(); err != nil {
85+
// session.Close()
86+
// return
87+
// }
7588
case <-idleTimer.C:
7689
// Close connection due to inactivity
7790
session.Close()

service/api/context.go

+14-7
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type APIResponse struct {
2929
// APIContext is the context for the API request
3030
type APIContext struct {
3131
echo.Context
32-
command string
32+
serverConfig *mcpserver.ServerConfig
3333
}
3434

3535
func createAPIMiddleware() echo.MiddlewareFunc {
@@ -49,12 +49,12 @@ func createAPIMiddleware() echo.MiddlewareFunc {
4949
return ctx.RespNoAuthMsg("no apikey")
5050
}
5151

52-
command := mcpserver.GetCommand(apikey)
53-
if command == "" {
52+
serverConfig := mcpserver.GetServerConfig(apikey)
53+
if serverConfig == nil {
5454
return ctx.RespNoAuthMsg("invalid apikey")
5555
}
5656

57-
ctx.command = command
57+
ctx.serverConfig = serverConfig
5858

5959
return next(ctx)
6060
}
@@ -86,12 +86,19 @@ func (c *APIContext) Valid(req interface{}) error {
8686
return nil
8787
}
8888

89-
func (c *APIContext) Command() string {
90-
return c.command
89+
// ServerConfig returns the server config
90+
func (c *APIContext) ServerConfig() *mcpserver.ServerConfig {
91+
return c.serverConfig
9192
}
9293

94+
// ServerCommand returns the server command
95+
func (c *APIContext) ServerCommand() string {
96+
return c.ServerConfig().Command
97+
}
98+
99+
// Connect connects to the mcp server
93100
func (c *APIContext) Connect() (*mcpclient.StdioClient, error) {
94-
command := c.Command()
101+
command := c.ServerCommand()
95102
if command == "" {
96103
return nil, fmt.Errorf("invalid command")
97104
}

service/jsonrpc/error.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,5 @@ var (
4040
ErrorInternalError = NewError(-32603, "Internal error", nil)
4141

4242
// ErrorProxyError is the error returned when the proxy error occurs.
43-
ErrorProxyError = NewError(-32000, "Proxy error", nil)
43+
ErrorProxyError = NewError(-32000, "Proxy error, Please restart client", nil)
4444
)

service/mcpclient/stdio.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"os/exec"
99
"sync"
10+
"time"
1011

1112
"github.com/chatmcp/mcprouter/service/jsonrpc"
1213
"github.com/tidwall/gjson"
@@ -106,7 +107,7 @@ func (c *StdioClient) listen() {
106107

107108
default:
108109
message, err := c.stdout.ReadBytes('\n')
109-
fmt.Printf("stdout read message: %s, %v\n", message, err)
110+
// fmt.Printf("stdout read message: %s, %v\n", message, err)
110111

111112
if err != nil {
112113
if err != io.EOF {
@@ -197,11 +198,17 @@ func (c *StdioClient) SendMessage(message []byte) ([]byte, error) {
197198
return nil, fmt.Errorf("failed to write request message: %w", err)
198199
}
199200

200-
fmt.Printf("stdin write request message: %s\n", message)
201+
// fmt.Printf("stdin write request message: %s\n", message)
202+
203+
timeout := time.After(30 * time.Second)
201204

202205
// wait for response
203206
for {
204207
select {
208+
case <-timeout:
209+
fmt.Println("timeout waiting for response after 30 seconds")
210+
c.Close()
211+
return nil, fmt.Errorf("timeout waiting for response after 30 seconds")
205212
case <-c.done:
206213
fmt.Println("client closed with no response")
207214
return nil, fmt.Errorf("client closed with no response")

service/mcpserver/command.go

+46-15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package mcpserver
22

33
import (
44
"bytes"
5+
"crypto/md5"
56
"encoding/json"
67
"fmt"
78
"io"
@@ -11,43 +12,73 @@ import (
1112
"github.com/tidwall/gjson"
1213
)
1314

14-
// GetCommand returns the command for the given key
15-
func GetCommand(key string) string {
16-
command := viper.GetString(fmt.Sprintf("mcp_server_commands.%s", key))
17-
if command == "" {
18-
return getRemoteCommand(key)
15+
// GetServerCommand returns the command for the given key
16+
func GetServerCommand(key string) string {
17+
config := GetServerConfig(key)
18+
if config == nil {
19+
return ""
20+
}
21+
22+
return config.Command
23+
}
24+
25+
// GetServerConfig returns the config for the given key
26+
func GetServerConfig(key string) *ServerConfig {
27+
config := &ServerConfig{}
28+
err := viper.UnmarshalKey(fmt.Sprintf("mcp_servers.%s", key), config)
29+
30+
if config.Command == "" {
31+
fmt.Printf("get local config failed: %v, try to get remote config\n", err)
32+
33+
config, err = getRemoteServerConfig(key)
34+
if err != nil {
35+
fmt.Printf("get remote config failed: %v\n", err)
36+
return nil
37+
}
1938
}
2039

21-
return command
40+
return config
2241
}
2342

24-
// getRemoteCommand returns the command for the given key from the remote API
25-
func getRemoteCommand(key string) string {
26-
apiUrl := viper.GetString("remote_apis.get_server_command")
43+
// getRemoteServerConfig returns the config for the given key from the remote API
44+
func getRemoteServerConfig(key string) (*ServerConfig, error) {
45+
apiUrl := viper.GetString("remote_apis.get_server_config")
2746

28-
fmt.Printf("get remote command from %s, with key: %s\n", apiUrl, key)
47+
fmt.Printf("get remote config from %s, with key: %s\n", apiUrl, key)
2948

3049
params := map[string]string{
3150
"server_key": key,
3251
}
3352

3453
jsonData, err := json.Marshal(params)
3554
if err != nil {
36-
return ""
55+
return nil, err
3756
}
3857

3958
response, err := http.Post(apiUrl, "application/json", bytes.NewBuffer(jsonData))
4059
if err != nil {
41-
return ""
60+
return nil, err
4261
}
4362

4463
body, err := io.ReadAll(response.Body)
4564
if err != nil {
46-
return ""
65+
return nil, err
4766
}
4867

4968
data := gjson.ParseBytes(body)
50-
command := data.Get("data.server_command").String()
5169

52-
return command
70+
config := &ServerConfig{
71+
ServerUUID: data.Get("data.server_uuid").String(),
72+
ServerName: data.Get("data.server_name").String(),
73+
ServerKey: data.Get("data.server_key").String(),
74+
Command: data.Get("data.command").String(),
75+
CommandHash: data.Get("data.command_hash").String(),
76+
ShareProcess: data.Get("data.share_process").Bool(),
77+
}
78+
79+
if config.CommandHash == "" {
80+
config.CommandHash = fmt.Sprintf("%x", md5.Sum([]byte(config.Command)))
81+
}
82+
83+
return config, nil
5384
}

service/mcpserver/config.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package mcpserver
2+
3+
// ServerConfig is the config for the remote mcp server
4+
type ServerConfig struct {
5+
ServerUUID string `json:"server_uuid,omitempty"`
6+
ServerName string `json:"server_name,omitempty"`
7+
ServerKey string `json:"server_key,omitempty"`
8+
Command string `json:"command"`
9+
CommandHash string `json:"command_hash,omitempty"`
10+
ShareProcess bool `json:"share_process,omitempty"`
11+
}

0 commit comments

Comments
 (0)