- pact gains MemberTokenScopeCatalog, MCPAdvertisedScopes and MCPCall - an empty MCP ScopesSupported resolves to the five fixed names including backend - pier copies advertised scopes and sets MCPCall beside WithDispatch
67 lines
2.4 KiB
Go
67 lines
2.4 KiB
Go
package pier
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
"github.com/modelcontextprotocol/go-sdk/auth"
|
|
"github.com/modelcontextprotocol/go-sdk/oauthex"
|
|
)
|
|
|
|
// PRMScopes is the scopes_supported list of the protected resource
|
|
// metadata when the published verifier does not advertise a non-empty
|
|
// list: the fixed MCP scopes, including backend.
|
|
var PRMScopes = []string{"read", "write", "ai", "offline_access", "backend"}
|
|
|
|
// mountPRM serves the RFC 9728 protected resource metadata at
|
|
// /.well-known/oauth-protected-resource and at
|
|
// /.well-known/oauth-protected-resource<mcp.path>, through the host's
|
|
// plain GET route kind. The authorization
|
|
// server is the published verifier's resource origin when a verifier is
|
|
// published, else app.url. A mcp.resource that differs from the verifier's
|
|
// resource is logged as a warning at boot (Pitfall 8).
|
|
func (p *Pier) mountPRM() error {
|
|
issuer := p.cfg.AppURL
|
|
scopes := append([]string(nil), PRMScopes...)
|
|
if verifier, ok := p.app.Lookup[pact.MCPTokenVerifier](); ok && verifier != nil {
|
|
if res := verifier.MCPResource(); res != "" {
|
|
if u, err := url.Parse(res); err == nil && u.Scheme != "" && u.Host != "" {
|
|
issuer = u.Scheme + "://" + u.Host
|
|
}
|
|
if res != p.cfg.Resource {
|
|
logger().Warn("pier: mcp.resource differs from the verifier's resource; clients that send the verifier's resource will be refused",
|
|
"mcp_resource", p.cfg.Resource, "verifier_resource", res)
|
|
}
|
|
}
|
|
if advertised, ok := verifier.(pact.MCPAdvertisedScopes); ok {
|
|
if list := advertised.MCPScopesSupported(); len(list) > 0 {
|
|
scopes = append([]string(nil), list...)
|
|
}
|
|
}
|
|
}
|
|
meta := &oauthex.ProtectedResourceMetadata{
|
|
Resource: p.cfg.Resource,
|
|
ScopesSupported: scopes,
|
|
BearerMethodsSupported: []string{"header"},
|
|
}
|
|
if issuer != "" {
|
|
meta.AuthorizationServers = []string{issuer}
|
|
}
|
|
h := nosniff(auth.ProtectedResourceMetadataHandler(meta))
|
|
for _, path := range []string{wellKnownPRM, wellKnownPRM + p.cfg.Path} {
|
|
if err := p.host.MountJSON(http.MethodGet, path, h); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// nosniff adds X-Content-Type-Options to a JSON document route.
|
|
func nosniff(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|