Skip to content

Commit b36d42d

Browse files
committed
Enhance search_servers documentation and update server entry structure
- Updated documentation to include repository information and separate MCP endpoints from source code repositories. - Added `source_code_url` field to `ServerEntry` struct for better clarity on source code links. - Modified tests to validate the new `source_code_url` field and ensure correct parsing of server entries.
1 parent a6ecda4 commit b36d42d

7 files changed

Lines changed: 71 additions & 80 deletions

File tree

docs/search_servers.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,22 @@ The search_servers functionality now includes an experimental repository detecti
2121
```
2222

2323
**Enhanced Output Format:**
24-
Results now include repository information when packages are detected:
24+
Results now include repository information when packages are detected and clearly separate MCP endpoints from source code repositories:
25+
26+
**Field Descriptions:**
27+
- `url`: **MCP endpoint URL only** - Direct connection URL for remote MCP servers (e.g., `https://weather.example.com/mcp`)
28+
- `source_code_url`: **Source code repository URL** - Link to the source code repository (e.g., GitHub URLs)
29+
- `connectUrl`: Alternative connection URL for remote servers
30+
- `installCmd`: Command to install the server locally (npm/pip)
31+
2532
```json
2633
[
2734
{
2835
"id": "weather-service",
2936
"name": "Weather MCP Server",
3037
"description": "Provides weather data via MCP",
3138
"url": "https://weather.example.com/mcp",
39+
"source_code_url": "https://github.com/example/weather-mcp-server",
3240
"installCmd": "npm install weather-mcp-server",
3341
"repository_info": {
3442
"npm": {
@@ -61,12 +69,13 @@ User Workflow:
6169
"id": "weather",
6270
"name": "WeatherInfo",
6371
"description": "Provides real-time weather data",
64-
"url": "https://weather.mcp.run/mcp/",
72+
"url": "https://weather.mcp.run/mcp/",
73+
"source_code_url": "https://github.com/mcprun/weather-service",
6574
"updatedAt": "2025-05-01T12:00:00Z"
6675
}
6776
]
6877

69-
In this example, the result indicates a server named “WeatherInfo”, with a base MCP endpoint URL. The user or agent can then call:
78+
In this example, the result indicates a server named “WeatherInfo”, with a base MCP endpoint URL for direct connection and a separate source code repository URL. The user or agent can then call:
7079

7180
mcpproxy upstream_servers add --name "WeatherInfo" --url "https://weather.mcp.run/mcp/"
7281

@@ -106,6 +115,7 @@ Example Output (for Example 1):
106115
"name": "WeatherInfo",
107116
"description": "Provides real-time weather data",
108117
"url": "https://weather.mcp.run/mcp/",
118+
"source_code_url": "https://github.com/mcprun/weather-service",
109119
"updatedAt": "2025-05-01T12:00:00Z"
110120
},
111121
{
@@ -114,11 +124,12 @@ Example Output (for Example 1):
114124
"name": "ForecastPro",
115125
"description": "7-day weather forecast tool",
116126
"url": "https://forecast.mcp.run/mcp/",
127+
"source_code_url": "https://github.com/mcprun/forecast-pro",
117128
"updatedAt": "2025-04-20T08:30:00Z"
118129
}
119130
]
120131

121-
In a real scenario, the above JSON could be printed to the console or returned as an MCP tool response. The user/agent sees two matching servers from MCP Run and can choose one to add. Each entry includes url (the base endpoint to connect to the MCP server) so that no additional lookup is required beyond this search.
132+
In a real scenario, the above JSON could be printed to the console or returned as an MCP tool response. The user/agent sees two matching servers from MCP Run and can choose one to add. Each entry includes url (the base endpoint to connect to the MCP server) and source_code_url (link to the source repository) so that no additional lookup is required beyond this search.
122133

123134
⚙️ Architecture & Flowchart
124135

internal/oauth/config.go

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import (
66
"mcpproxy-go/internal/config"
77
"net"
88
"net/http"
9-
"net/url"
109
"os"
11-
"strings"
1210
"sync"
1311
"time"
1412

@@ -90,15 +88,6 @@ func CreateOAuthConfig(serverConfig *config.ServerConfig) *client.OAuthConfig {
9088

9189
// Determine the correct OAuth server metadata URL based on the server URL
9290
var authServerMetadataURL string
93-
if serverConfig.URL != "" {
94-
// Extract base URL from server URL and construct the well-known metadata endpoint
95-
if u, err := parseBaseURL(serverConfig.URL); err == nil {
96-
authServerMetadataURL = u + "/.well-known/oauth-authorization-server"
97-
logger.Debug("Setting OAuth server metadata URL",
98-
zap.String("server", serverConfig.Name),
99-
zap.String("auth_server_metadata_url", authServerMetadataURL))
100-
}
101-
}
10291

10392
oauthConfig := &client.OAuthConfig{
10493
ClientID: "", // Will be obtained via Dynamic Client Registration
@@ -115,7 +104,7 @@ func CreateOAuthConfig(serverConfig *config.ServerConfig) *client.OAuthConfig {
115104
zap.Strings("scopes", scopes),
116105
zap.Bool("pkce_enabled", true),
117106
zap.String("redirect_uri", callbackServer.RedirectURI),
118-
zap.String("auth_server_metadata_url", authServerMetadataURL))
107+
zap.String("discovery_mode", "automatic")) // Changed from explicit metadata URL to automatic discovery
119108

120109
return oauthConfig
121110
}
@@ -281,29 +270,6 @@ func GetGlobalCallbackManager() *CallbackServerManager {
281270
return globalCallbackManager
282271
}
283272

284-
// parseBaseURL extracts the base URL (scheme + host) from a full URL
285-
func parseBaseURL(fullURL string) (string, error) {
286-
if fullURL == "" {
287-
return "", fmt.Errorf("empty URL")
288-
}
289-
290-
// Handle URLs that might not have a scheme
291-
if !strings.HasPrefix(fullURL, "http://") && !strings.HasPrefix(fullURL, "https://") {
292-
fullURL = "https://" + fullURL
293-
}
294-
295-
u, err := url.Parse(fullURL)
296-
if err != nil {
297-
return "", fmt.Errorf("invalid URL: %w", err)
298-
}
299-
300-
if u.Scheme == "" || u.Host == "" {
301-
return "", fmt.Errorf("invalid URL: missing scheme or host")
302-
}
303-
304-
return fmt.Sprintf("%s://%s", u.Scheme, u.Host), nil
305-
}
306-
307273
// ShouldUseOAuth determines if OAuth should be attempted for a given server
308274
// Headers are tried first if configured, then OAuth as fallback on auth errors
309275
func ShouldUseOAuth(serverConfig *config.ServerConfig) bool {

internal/registries/new_parsers_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ func TestParseAzureMCPDemo(t *testing.T) {
5757
if server.Name != "io.github.21st-dev/magic-mcp" {
5858
t.Errorf("Expected name 'io.github.21st-dev/magic-mcp', got '%s'", server.Name)
5959
}
60-
if server.URL != "https://github.com/21st-dev/magic-mcp" {
61-
t.Errorf("Expected URL 'https://github.com/21st-dev/magic-mcp', got '%s'", server.URL)
60+
if server.SourceCodeURL != "https://github.com/21st-dev/magic-mcp" {
61+
t.Errorf("Expected SourceCodeURL 'https://github.com/21st-dev/magic-mcp', got '%s'", server.SourceCodeURL)
6262
}
6363
if server.UpdatedAt != "2025-05-15T04:52:52Z" {
6464
t.Errorf("Expected UpdatedAt '2025-05-15T04:52:52Z', got '%s'", server.UpdatedAt)

internal/registries/search.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,9 @@ func applyBatchRepositoryGuessing(ctx context.Context, servers []ServerEntry, gu
166166
server := &servers[i]
167167
var githubURL string
168168

169-
// Check if server has a URL that looks like a GitHub repository
170-
if server.URL != "" && isGitHubURL(server.URL) {
171-
githubURL = server.URL
169+
// Check if server has a SourceCodeURL that looks like a GitHub repository
170+
if server.SourceCodeURL != "" && isGitHubURL(server.SourceCodeURL) {
171+
githubURL = server.SourceCodeURL
172172
}
173173

174174
// Add to batch if we found a GitHub URL
@@ -735,7 +735,7 @@ func parsePulseWithoutGuesser(rawData interface{}) []ServerEntry {
735735

736736
// Store source_code_url for later batch processing
737737
if sourceCodeURL, ok := itemMap["source_code_url"].(string); ok && sourceCodeURL != "" {
738-
server.URL = sourceCodeURL
738+
server.SourceCodeURL = sourceCodeURL
739739
}
740740

741741
servers = append(servers, server)
@@ -788,7 +788,7 @@ func parseAzureMCPDemoWithoutGuesser(rawData interface{}) []ServerEntry {
788788
if repo, ok := itemMap["repository"].(map[string]interface{}); ok {
789789
if repoURL, ok := repo["url"].(string); ok && repoURL != "" {
790790
// Store repository URL for later batch processing
791-
server.URL = repoURL
791+
server.SourceCodeURL = repoURL
792792
}
793793
}
794794

internal/registries/search_test.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,22 +1085,22 @@ func TestApplyBatchRepositoryGuessing(t *testing.T) {
10851085
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
10861086
defer cancel()
10871087

1088-
// Create test servers with GitHub URLs
1088+
// Create test servers with GitHub URLs in SourceCodeURL field
10891089
servers := []ServerEntry{
10901090
{
1091-
ID: "server1",
1092-
Name: "Test Server 1",
1093-
URL: "https://github.com/facebook/react",
1091+
ID: "server1",
1092+
Name: "Test Server 1",
1093+
SourceCodeURL: "https://github.com/facebook/react",
10941094
},
10951095
{
1096-
ID: "server2",
1097-
Name: "Test Server 2",
1098-
URL: "https://github.com/nonexistent/package123",
1096+
ID: "server2",
1097+
Name: "Test Server 2",
1098+
SourceCodeURL: "https://github.com/nonexistent/package123",
10991099
},
11001100
{
11011101
ID: "server3",
11021102
Name: "Test Server 3",
1103-
URL: "https://example.com/not-github", // Non-GitHub URL
1103+
URL: "https://example.com/not-github", // Non-GitHub URL (this is an MCP endpoint)
11041104
},
11051105
{
11061106
ID: "server4",
@@ -1149,22 +1149,22 @@ func TestApplyBatchRepositoryGuessing_DuplicateURLs(t *testing.T) {
11491149
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
11501150
defer cancel()
11511151

1152-
// Create servers with duplicate GitHub URLs
1152+
// Create servers with duplicate GitHub URLs in SourceCodeURL field
11531153
servers := []ServerEntry{
11541154
{
1155-
ID: "server1",
1156-
Name: "Test Server 1",
1157-
URL: "https://github.com/facebook/react",
1155+
ID: "server1",
1156+
Name: "Test Server 1",
1157+
SourceCodeURL: "https://github.com/facebook/react",
11581158
},
11591159
{
1160-
ID: "server2",
1161-
Name: "Test Server 2",
1162-
URL: "https://github.com/lodash/lodash",
1160+
ID: "server2",
1161+
Name: "Test Server 2",
1162+
SourceCodeURL: "https://github.com/lodash/lodash",
11631163
},
11641164
{
1165-
ID: "server3",
1166-
Name: "Test Server 3",
1167-
URL: "https://github.com/facebook/react", // Duplicate URL
1165+
ID: "server3",
1166+
Name: "Test Server 3",
1167+
SourceCodeURL: "https://github.com/facebook/react", // Duplicate URL
11681168
},
11691169
}
11701170

@@ -1271,15 +1271,15 @@ func TestParsePulseWithoutGuesser(t *testing.T) {
12711271
assert.Equal(t, "Express MCP server for web development", servers[0].Description)
12721272
assert.Equal(t, "npx -y express-mcp-server", servers[0].InstallCmd)
12731273
assert.Equal(t, "https://express.example.com/mcp", servers[0].ConnectURL)
1274-
assert.Equal(t, "https://github.com/user/express-mcp-server", servers[0].URL)
1274+
assert.Equal(t, "https://github.com/user/express-mcp-server", servers[0].SourceCodeURL)
12751275

12761276
// Second server
12771277
assert.Equal(t, "weather-server", servers[1].ID)
12781278
assert.Equal(t, "weather-server", servers[1].Name)
12791279
assert.Equal(t, "Weather data MCP server", servers[1].Description)
12801280
assert.Equal(t, "", servers[1].InstallCmd) // No package info
12811281
assert.Equal(t, "", servers[1].ConnectURL)
1282-
assert.Equal(t, "https://github.com/user/weather-server", servers[1].URL)
1282+
assert.Equal(t, "https://github.com/user/weather-server", servers[1].SourceCodeURL)
12831283
}
12841284

12851285
func TestParseAzureMCPDemoWithoutGuesser(t *testing.T) {
@@ -1309,7 +1309,7 @@ func TestParseAzureMCPDemoWithoutGuesser(t *testing.T) {
13091309
assert.Equal(t, "azure-demo-1", server.ID)
13101310
assert.Equal(t, "Azure Demo Server", server.Name)
13111311
assert.Equal(t, "Demo server for Azure MCP (v1.0.0)", server.Description)
1312-
assert.Equal(t, "https://github.com/microsoft/azure-mcp-demo", server.URL)
1312+
assert.Equal(t, "https://github.com/microsoft/azure-mcp-demo", server.SourceCodeURL)
13131313
assert.Equal(t, "2024-01-01", server.UpdatedAt)
13141314
}
13151315

internal/registries/types.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ type RegistryEntry struct {
1616

1717
// ServerEntry represents an MCP server discovered via a registry
1818
type ServerEntry struct {
19-
ID string `json:"id"`
20-
Name string `json:"name"`
21-
Description string `json:"description"`
22-
URL string `json:"url"` // MCP endpoint for direct connection
23-
InstallCmd string `json:"installCmd,omitempty"` // Command to install the server locally
24-
ConnectURL string `json:"connectUrl,omitempty"` // Alternative connection URL for remote servers
25-
UpdatedAt string `json:"updatedAt,omitempty"`
26-
CreatedAt string `json:"createdAt,omitempty"`
27-
Registry string `json:"registry,omitempty"` // Which registry this came from
19+
ID string `json:"id"`
20+
Name string `json:"name"`
21+
Description string `json:"description"`
22+
URL string `json:"url"` // MCP endpoint for remote server connections only
23+
SourceCodeURL string `json:"source_code_url,omitempty"` // URL to source code repository
24+
InstallCmd string `json:"installCmd,omitempty"` // Command to install the server locally
25+
ConnectURL string `json:"connectUrl,omitempty"` // Alternative connection URL for remote servers
26+
UpdatedAt string `json:"updatedAt,omitempty"`
27+
CreatedAt string `json:"createdAt,omitempty"`
28+
Registry string `json:"registry,omitempty"` // Which registry this came from
2829

2930
// Repository detection information
3031
RepositoryInfo *experiments.GuessResult `json:"repository_info,omitempty"` // Detected npm/pypi package info

internal/upstream/client.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,15 +1217,28 @@ func (c *Client) performOAuthAuthorization(ctx context.Context, oauthHandler *mc
12171217
zap.String("server", c.config.Name),
12181218
zap.String("redirect_uri", callbackServer.RedirectURI))
12191219

1220-
// Step 1: Dynamic Client Registration
1220+
// Step 1: Dynamic Client Registration with panic recovery
12211221
c.logger.Info("Performing Dynamic Client Registration",
12221222
zap.String("server", c.config.Name))
12231223

1224-
if err := oauthHandler.RegisterClient(ctx, "mcpproxy-go"); err != nil {
1224+
var registrationErr error
1225+
func() {
1226+
defer func() {
1227+
if r := recover(); r != nil {
1228+
c.logger.Error("Dynamic Client Registration panicked",
1229+
zap.String("server", c.config.Name),
1230+
zap.Any("panic", r))
1231+
registrationErr = fmt.Errorf("Dynamic Client Registration failed due to invalid OAuth server metadata: %v", r)
1232+
}
1233+
}()
1234+
registrationErr = oauthHandler.RegisterClient(ctx, "mcpproxy-go")
1235+
}()
1236+
1237+
if registrationErr != nil {
12251238
c.logger.Error("Dynamic Client Registration failed",
12261239
zap.String("server", c.config.Name),
1227-
zap.Error(err))
1228-
return fmt.Errorf("Dynamic Client Registration failed: %w", err)
1240+
zap.Error(registrationErr))
1241+
return fmt.Errorf("Dynamic Client Registration failed: %w", registrationErr)
12291242
}
12301243

12311244
c.logger.Info("Dynamic Client Registration successful",

0 commit comments

Comments
 (0)