feat: add --tools flag to filter exposed MCP tools (#167)

Adds `-O`/`-tools` CLI flag and `GITEA_TOOLS` environment variable
accepting a comma-separated list of tool names. When set, only the
listed tools are exposed to MCP clients, which lets AI agents trim
their tool context. Composes with `--read-only`. Unknown names are
logged at startup so typos surface instead of failing silently.

Co-Authored-By: silverwind <me@silverwind.io>
Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
This commit is contained in:
Martin Mikula
2026-05-10 12:07:16 +02:00
committed by silverwind
parent cd82f6f207
commit bcefbaa9c1
5 changed files with 181 additions and 50 deletions
+47 -7
View File
@@ -1,7 +1,11 @@
package tool
import (
"slices"
"strings"
"gitea.com/gitea/gitea-mcp/pkg/flag"
"gitea.com/gitea/gitea-mcp/pkg/log"
"github.com/mark3labs/mcp-go/server"
)
@@ -27,12 +31,48 @@ func (t *Tool) RegisterRead(s server.ServerTool) {
}
func (t *Tool) Tools() []server.ServerTool {
tools := make([]server.ServerTool, 0, len(t.write)+len(t.read))
if flag.ReadOnly {
tools = append(tools, t.read...)
return tools
all := make([]server.ServerTool, 0, len(t.write)+len(t.read))
if !flag.ReadOnly {
all = append(all, t.write...)
}
tools = append(tools, t.write...)
tools = append(tools, t.read...)
return tools
all = append(all, t.read...)
if len(flag.AllowedTools) == 0 {
return all
}
filtered := make([]server.ServerTool, 0, len(all))
for _, st := range all {
if _, ok := flag.AllowedTools[st.Tool.Name]; ok {
filtered = append(filtered, st)
}
}
return filtered
}
// WarnUnmatchedAllowedTools logs any names in flag.AllowedTools that don't
// match a tool registered on any of the given domains. No-op if the allowlist
// is empty.
func WarnUnmatchedAllowedTools(domains ...*Tool) {
if len(flag.AllowedTools) == 0 {
return
}
known := map[string]struct{}{}
for _, d := range domains {
for _, st := range d.read {
known[st.Tool.Name] = struct{}{}
}
for _, st := range d.write {
known[st.Tool.Name] = struct{}{}
}
}
var unmatched []string
for name := range flag.AllowedTools {
if _, ok := known[name]; !ok {
unmatched = append(unmatched, name)
}
}
if len(unmatched) == 0 {
return
}
slices.Sort(unmatched)
log.Warnf("Unknown tools in --tools allowlist (ignored): %s", strings.Join(unmatched, ", "))
}