Skip to content

Commit a4cfa7d

Browse files
committed
Add support tools filtering.
Add tool filtering functionality that intercepts MCP tools/list and tools/call requests using Cedar policies; unauthorized tool calls return HTTP 200 with JSON-RPC "Invalid params" error (-32602); includes template-based policy generation, response filtering, and extensive test coverage for both allowed and denied scenarios. An additional option `--tools` is available for the `thv run` command.
1 parent 36b649c commit a4cfa7d

File tree

9 files changed

+1000
-4
lines changed

9 files changed

+1000
-4
lines changed

cmd/thv/app/run.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ var (
9292

9393
// Network isolation flag
9494
runIsolateNetwork bool
95+
96+
// Tool filtering flag
97+
runTools []string
9598
)
9699

97100
func init() {
@@ -225,6 +228,10 @@ func init() {
225228
runCmd.Flags().BoolVar(&runIsolateNetwork, "isolate-network", false,
226229
"Isolate the container network from the host (default: false)")
227230

231+
// Tool filtering flag
232+
runCmd.Flags().StringArrayVar(&runTools, "tools", []string{},
233+
"Comma-separated list of tools to enable (e.g., --tools=weather,calculator). If not specified, all tools are enabled.")
234+
228235
}
229236

230237
func getOidcFromFlags(cmd *cobra.Command) (string, string, string, string, bool, error) {
@@ -383,6 +390,7 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
383390
runThvCABundle,
384391
runJWKSAuthTokenFile,
385392
runJWKSAllowPrivateIP,
393+
runTools,
386394
envVarValidator,
387395
types.ProxyMode(runProxyMode),
388396
)

pkg/authz/middleware.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@ var MCPMethodToFeatureOperation = map[string]struct {
3030
"initialize": {Feature: "", Operation: ""}, // Always allowed
3131
}
3232

33-
// shouldSkipInitialAuthorization checks if the request should skip authorization
34-
// before reading the request body.
35-
func shouldSkipInitialAuthorization(r *http.Request) bool {
33+
// shouldSkip checks if the request should skip authorization
34+
func shouldSkip(r *http.Request) bool {
3635
// Skip authorization for non-POST requests and non-JSON content types
3736
if r.Method != http.MethodPost || !strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") {
3837
return true
@@ -138,7 +137,7 @@ func handleUnauthorized(w http.ResponseWriter, msgID interface{}, err error) {
138137
func (a *CedarAuthorizer) Middleware(next http.Handler) http.Handler {
139138
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
140139
// Check if we should skip authorization before checking parsed data
141-
if shouldSkipInitialAuthorization(r) {
140+
if shouldSkip(r) {
142141
next.ServeHTTP(w, r)
143142
return
144143
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Tool filtering policy template
2+
// This policy allows access to specific tools based on the --tools flag
3+
// The tools are interpolated into this template at runtime
4+
5+
// Allow access to specific tools only{{range .Tools}}
6+
permit(principal, action == Action::"call_tool", resource == Tool::"{{.}}");{{end}}
7+
8+
// Allow listing of tools (for tools/list operations)
9+
permit(principal, action == Action::"list_tools", resource == FeatureType::"tool");

0 commit comments

Comments
 (0)