Skip to content

Commit 3425c73

Browse files
wendorftuckerchapin
andcommitted
MCP server now proxies any Authorization headers it receives
This doesn't affect stdio transport, but for streamable http servers any number of tokens can be used in parallel. Co-authored-by: Tucker Chapin <[email protected]>
1 parent f70359d commit 3425c73

File tree

6 files changed

+91
-16
lines changed

6 files changed

+91
-16
lines changed

cmd/server.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import (
55
"os"
66

77
"github.com/mark3labs/mcp-go/server"
8+
"github.com/render-oss/render-mcp-server/pkg/authn"
89
"github.com/render-oss/render-mcp-server/pkg/cfg"
910
"github.com/render-oss/render-mcp-server/pkg/client"
1011
"github.com/render-oss/render-mcp-server/pkg/deploy"
1112
"github.com/render-oss/render-mcp-server/pkg/keyvalue"
1213
"github.com/render-oss/render-mcp-server/pkg/logs"
14+
"github.com/render-oss/render-mcp-server/pkg/multicontext"
1315
"github.com/render-oss/render-mcp-server/pkg/owner"
1416
"github.com/render-oss/render-mcp-server/pkg/postgres"
1517
"github.com/render-oss/render-mcp-server/pkg/service"
@@ -48,11 +50,21 @@ func Serve(transport string) *server.MCPServer {
4850
log.Print("using in-memory session store\n")
4951
sessionStore = session.NewInMemoryStore()
5052
}
51-
if err := server.NewStreamableHTTPServer(s, server.WithHTTPContextFunc(session.ContextWithHTTPSession(sessionStore))).Start(":10000"); err != nil {
53+
err := server.
54+
NewStreamableHTTPServer(s, server.WithHTTPContextFunc(multicontext.MultiHTTPContextFunc(
55+
session.ContextWithHTTPSession(sessionStore),
56+
authn.ContextWithAPITokenFromHeader,
57+
))).
58+
Start(":10000")
59+
if err != nil {
5260
log.Fatalf("Starting Streamable server: %v\n:", err)
5361
}
5462
} else {
55-
if err := server.ServeStdio(s, server.WithStdioContextFunc(session.ContextWithStdioSession)); err != nil {
63+
err := server.ServeStdio(s, server.WithStdioContextFunc(multicontext.MultiStdioContextFunc(
64+
session.ContextWithStdioSession,
65+
authn.ContextWithAPITokenFromConfig,
66+
)))
67+
if err != nil {
5668
log.Fatalf("Starting STDIO server: %v\n", err)
5769
}
5870
}

pkg/authn/authn.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package authn
2+
3+
import (
4+
"context"
5+
"errors"
6+
"log"
7+
"net/http"
8+
9+
"github.com/render-oss/render-mcp-server/pkg/cfg"
10+
)
11+
12+
const apiTokenKey string = "token"
13+
14+
var ErrNotAuthorized = errors.New("resource not found")
15+
16+
func APITokenFromContext(ctx context.Context) string {
17+
if token, ok := ctx.Value(apiTokenKey).(string); ok {
18+
return token
19+
}
20+
return ""
21+
}
22+
23+
func ContextWithAPIToken(ctx context.Context, token string) context.Context {
24+
return context.WithValue(ctx, apiTokenKey, token)
25+
}
26+
27+
func ContextWithAPITokenFromHeader(ctx context.Context, req *http.Request) context.Context {
28+
token := req.Header.Get("Authorization")
29+
30+
if token == "" {
31+
return ctx
32+
}
33+
34+
// Note: we strip the "Bearer " prefix if it exists
35+
// MCP Inspector attaches this prefix automatically, but it's unclear how standard this is
36+
if len(token) > 7 && token[:7] == "Bearer " {
37+
token = token[7:]
38+
}
39+
40+
return ContextWithAPIToken(ctx, token)
41+
}
42+
43+
func ContextWithAPITokenFromConfig(ctx context.Context) context.Context {
44+
token := cfg.GetAPIKey()
45+
if token == "" {
46+
log.Fatal("Error getting API token from config")
47+
}
48+
return ContextWithAPIToken(ctx, token)
49+
}

pkg/client/client.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/http"
99
"reflect"
1010

11+
"github.com/render-oss/render-mcp-server/pkg/authn"
1112
"github.com/render-oss/render-mcp-server/pkg/cfg"
1213
"github.com/render-oss/render-mcp-server/pkg/config"
1314
)
@@ -92,7 +93,7 @@ func firstNonNilErrorField(response any) *ErrorWithCode {
9293

9394
func clientWithAuth(httpClient *http.Client, apiCfg config.APIConfig) (*ClientWithResponses, error) {
9495
insertAuth := func(ctx context.Context, req *http.Request) error {
95-
req.Header = AddHeaders(req.Header, apiCfg.Key)
96+
req.Header = AddHeaders(req.Header, authn.APITokenFromContext(ctx))
9697
return nil
9798
}
9899

pkg/config/config.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ type Config struct {
2929
}
3030

3131
type APIConfig struct {
32-
Key string `yaml:"key,omitempty"`
3332
ExpiresAt int64 `yaml:"expires_at,omitempty"`
3433
Host string `json:"host,omitempty"`
3534
RefreshToken string `json:"refresh_token,omitempty"`
@@ -61,18 +60,9 @@ func init() {
6160

6261
func DefaultAPIConfig() (APIConfig, error) {
6362
apiCfg := APIConfig{
64-
Key: cfg.GetAPIKey(),
6563
Host: cfg.GetHost(),
6664
}
6765

68-
var err error
69-
if apiCfg.Key == "" {
70-
apiCfg, err = getAPIConfig()
71-
if err != nil || apiCfg.Key == "" {
72-
return APIConfig{}, ErrLogin
73-
}
74-
}
75-
7666
if apiCfg.Host == "" {
7767
apiCfg.Host = cfg.GetHost()
7868
}
@@ -159,7 +149,6 @@ func SetAPIConfig(input APIConfig) error {
159149
}
160150

161151
cfg.Host = input.Host
162-
cfg.Key = input.Key
163152
cfg.ExpiresAt = input.ExpiresAt
164153
cfg.RefreshToken = input.RefreshToken
165154
return cfg.Persist()

pkg/multicontext/multicontext.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package multicontext
2+
3+
import (
4+
"context"
5+
"net/http"
6+
7+
"github.com/mark3labs/mcp-go/server"
8+
)
9+
10+
func MultiHTTPContextFunc(fns ...server.HTTPContextFunc) server.HTTPContextFunc {
11+
return func(ctx context.Context, r *http.Request) context.Context {
12+
for _, fn := range fns {
13+
ctx = fn(ctx, r)
14+
}
15+
return ctx
16+
}
17+
}
18+
19+
func MultiStdioContextFunc(fns ...server.StdioContextFunc) server.StdioContextFunc {
20+
return func(ctx context.Context) context.Context {
21+
for _, fn := range fns {
22+
ctx = fn(ctx)
23+
}
24+
return ctx
25+
}
26+
}

render.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ services:
66
buildCommand: go build -tags netgo -ldflags '-s -w' -o app
77
startCommand: ./app --transport http
88
envVars:
9-
- key: RENDER_API_KEY
10-
sync: false
119
- key: REDIS_URL
1210
fromService:
1311
name: mcp-kv

0 commit comments

Comments
 (0)