Skip to content

Commit bb53bb5

Browse files
committed
Refactor URI Template Matching with uritemplate Library
1 parent 328a25d commit bb53bb5

File tree

4 files changed

+10
-68
lines changed

4 files changed

+10
-68
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ go 1.23.0
44

55
require (
66
github.com/google/go-cmp v0.7.0
7+
github.com/yosida95/uritemplate/v3 v3.0.2
78
golang.org/x/tools v0.34.0
89
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
22
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
3+
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
4+
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
35
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
46
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=

mcp/resource.go

Lines changed: 3 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import (
1313
"net/url"
1414
"os"
1515
"path/filepath"
16-
"regexp"
1716
"strings"
1817

1918
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
2019
"github.com/modelcontextprotocol/go-sdk/internal/util"
20+
"github.com/yosida95/uritemplate/v3"
2121
)
2222

2323
// A ServerResource associates a Resource with its handler.
@@ -155,67 +155,10 @@ func fileRoot(root *Root) (_ string, err error) {
155155
}
156156

157157
// Matches reports whether the receiver's uri template matches the uri.
158-
// TODO: use "github.com/yosida95/uritemplate/v3"
159158
func (sr *ServerResourceTemplate) Matches(uri string) bool {
160-
re, err := uriTemplateToRegexp(sr.ResourceTemplate.URITemplate)
159+
tmpl, err := uritemplate.New(sr.ResourceTemplate.URITemplate)
161160
if err != nil {
162161
return false
163162
}
164-
return re.MatchString(uri)
165-
}
166-
167-
func uriTemplateToRegexp(uriTemplate string) (*regexp.Regexp, error) {
168-
pat := uriTemplate
169-
var b strings.Builder
170-
b.WriteByte('^')
171-
seen := map[string]bool{}
172-
for len(pat) > 0 {
173-
literal, rest, ok := strings.Cut(pat, "{")
174-
b.WriteString(regexp.QuoteMeta(literal))
175-
if !ok {
176-
break
177-
}
178-
expr, rest, ok := strings.Cut(rest, "}")
179-
if !ok {
180-
return nil, errors.New("missing '}'")
181-
}
182-
pat = rest
183-
if strings.ContainsRune(expr, ',') {
184-
return nil, errors.New("can't handle commas in expressions")
185-
}
186-
if strings.ContainsRune(expr, ':') {
187-
return nil, errors.New("can't handle prefix modifiers in expressions")
188-
}
189-
if len(expr) > 0 && expr[len(expr)-1] == '*' {
190-
return nil, errors.New("can't handle explode modifiers in expressions")
191-
}
192-
193-
// These sets of valid characters aren't accurate.
194-
// See https://datatracker.ietf.org/doc/html/rfc6570.
195-
var re, name string
196-
first := byte(0)
197-
if len(expr) > 0 {
198-
first = expr[0]
199-
}
200-
switch first {
201-
default:
202-
// {var} doesn't match slashes. (It should also fail to match other characters,
203-
// but this simplified implementation doesn't handle that.)
204-
re = `[^/]*`
205-
name = expr
206-
case '+':
207-
// {+var} matches anything, even slashes
208-
re = `.*`
209-
name = expr[1:]
210-
case '#', '.', '/', ';', '?', '&':
211-
return nil, fmt.Errorf("prefix character %c unsupported", first)
212-
}
213-
if seen[name] {
214-
return nil, fmt.Errorf("can't handle duplicate name %q", name)
215-
}
216-
seen[name] = true
217-
b.WriteString(re)
218-
}
219-
b.WriteByte('$')
220-
return regexp.Compile(b.String())
163+
return tmpl.Regexp().MatchString(uri)
221164
}

mcp/resource_test.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,18 +119,14 @@ func TestTemplateMatch(t *testing.T) {
119119
template string
120120
want bool
121121
}{
122-
{"file:///{}/{a}/{b}", true},
122+
{"file:///{}/{a}/{b}", false}, // invalid: empty variable expression "{}" is not allowed in RFC 6570
123123
{"file:///{a}/{b}", false},
124124
{"file:///{+path}", true},
125125
{"file:///{a}/{+path}", true},
126126
} {
127-
re, err := uriTemplateToRegexp(tt.template)
128-
if err != nil {
129-
t.Fatalf("%s: %v", tt.template, err)
130-
}
131-
got := re.MatchString(uri)
132-
if got != tt.want {
133-
t.Errorf("%s: got %t, want %t", tt.template, got, tt.want)
127+
resourceTmpl := ServerResourceTemplate{ResourceTemplate: &ResourceTemplate{URITemplate: tt.template}}
128+
if matched := resourceTmpl.Matches(uri); matched != tt.want {
129+
t.Errorf("%s: got %t, want %t", tt.template, matched, tt.want)
134130
}
135131
}
136132
}

0 commit comments

Comments
 (0)