mcp/Resource: Refactor URI Template Matching with uritemplate Library (#100)
1. Replaced the `uriTemplateToRegexp` function with the use of [yosida95/uritemplate](https://github.com/yosida95/uritemplate) to handle URI template matching as per the request. 2. Updated the relevant test cases to verify the new implementation and fixed an invalid URI template (`"file:///{}/{a}/{b}"`) that doesn't conform to RFC 6570 due to an empty `{}` expression. 3. Updated `go.mod` and `go.sum` to reflect the new dependency. Fixes #11.
This commit is contained in:
parent
cdf34cc9da
commit
fbff31af14
4 changed files with 10 additions and 68 deletions
1
go.mod
1
go.mod
|
|
@ -4,5 +4,6 @@ go 1.23.0
|
|||
|
||||
require (
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2
|
||||
golang.org/x/tools v0.34.0
|
||||
)
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -1,4 +1,6 @@
|
|||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
||||
"github.com/modelcontextprotocol/go-sdk/internal/util"
|
||||
"github.com/yosida95/uritemplate/v3"
|
||||
)
|
||||
|
||||
// A serverResource associates a Resource with its handler.
|
||||
|
|
@ -155,67 +155,10 @@ func fileRoot(root *Root) (_ string, err error) {
|
|||
}
|
||||
|
||||
// Matches reports whether the receiver's uri template matches the uri.
|
||||
// TODO: use "github.com/yosida95/uritemplate/v3"
|
||||
func (sr *serverResourceTemplate) Matches(uri string) bool {
|
||||
re, err := uriTemplateToRegexp(sr.resourceTemplate.URITemplate)
|
||||
tmpl, err := uritemplate.New(sr.resourceTemplate.URITemplate)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return re.MatchString(uri)
|
||||
}
|
||||
|
||||
func uriTemplateToRegexp(uriTemplate string) (*regexp.Regexp, error) {
|
||||
pat := uriTemplate
|
||||
var b strings.Builder
|
||||
b.WriteByte('^')
|
||||
seen := map[string]bool{}
|
||||
for len(pat) > 0 {
|
||||
literal, rest, ok := strings.Cut(pat, "{")
|
||||
b.WriteString(regexp.QuoteMeta(literal))
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
expr, rest, ok := strings.Cut(rest, "}")
|
||||
if !ok {
|
||||
return nil, errors.New("missing '}'")
|
||||
}
|
||||
pat = rest
|
||||
if strings.ContainsRune(expr, ',') {
|
||||
return nil, errors.New("can't handle commas in expressions")
|
||||
}
|
||||
if strings.ContainsRune(expr, ':') {
|
||||
return nil, errors.New("can't handle prefix modifiers in expressions")
|
||||
}
|
||||
if len(expr) > 0 && expr[len(expr)-1] == '*' {
|
||||
return nil, errors.New("can't handle explode modifiers in expressions")
|
||||
}
|
||||
|
||||
// These sets of valid characters aren't accurate.
|
||||
// See https://datatracker.ietf.org/doc/html/rfc6570.
|
||||
var re, name string
|
||||
first := byte(0)
|
||||
if len(expr) > 0 {
|
||||
first = expr[0]
|
||||
}
|
||||
switch first {
|
||||
default:
|
||||
// {var} doesn't match slashes. (It should also fail to match other characters,
|
||||
// but this simplified implementation doesn't handle that.)
|
||||
re = `[^/]*`
|
||||
name = expr
|
||||
case '+':
|
||||
// {+var} matches anything, even slashes
|
||||
re = `.*`
|
||||
name = expr[1:]
|
||||
case '#', '.', '/', ';', '?', '&':
|
||||
return nil, fmt.Errorf("prefix character %c unsupported", first)
|
||||
}
|
||||
if seen[name] {
|
||||
return nil, fmt.Errorf("can't handle duplicate name %q", name)
|
||||
}
|
||||
seen[name] = true
|
||||
b.WriteString(re)
|
||||
}
|
||||
b.WriteByte('$')
|
||||
return regexp.Compile(b.String())
|
||||
return tmpl.Regexp().MatchString(uri)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,18 +119,14 @@ func TestTemplateMatch(t *testing.T) {
|
|||
template string
|
||||
want bool
|
||||
}{
|
||||
{"file:///{}/{a}/{b}", true},
|
||||
{"file:///{}/{a}/{b}", false}, // invalid: empty variable expression "{}" is not allowed in RFC 6570
|
||||
{"file:///{a}/{b}", false},
|
||||
{"file:///{+path}", true},
|
||||
{"file:///{a}/{+path}", true},
|
||||
} {
|
||||
re, err := uriTemplateToRegexp(tt.template)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", tt.template, err)
|
||||
}
|
||||
got := re.MatchString(uri)
|
||||
if got != tt.want {
|
||||
t.Errorf("%s: got %t, want %t", tt.template, got, tt.want)
|
||||
resourceTmpl := serverResourceTemplate{resourceTemplate: &ResourceTemplate{URITemplate: tt.template}}
|
||||
if matched := resourceTmpl.Matches(uri); matched != tt.want {
|
||||
t.Errorf("%s: got %t, want %t", tt.template, matched, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue