[Feature Request] Add ability to whitelist tools #434
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/OpenSpec
Kind/Security
Kind/Testing
Priority/Critical
Priority/High
Priority/Low
Priority/Medium
RFC - Request For Comments
Reviewed/Confirmed
Reviewed/Duplicate
Reviewed/Invalid
Reviewed/Won't Fix
Status/Abandoned
Status/Blocked
Status/Need More Info
approved
area:client-host
area:item-interaction
area:meta-tooling
area:operator-dev-loop
area:repository-hygiene
area:typescript-sdk
area:website
blocked
bug
castra:bot-active
castra:human
change
checker:tobi
close-suggested
code
copilot-conflict
deferred
dependencies
docs-ok
enhancement
fast-track
groom:close-suggested
hermes-attempted
hermes-needs-clarification
hermes-ready
hermes-review
hermes-wip
human
human-required
intake
merge
merge-conflict
needs-scope
no-concept-doc
no-demo
plan-approved
plan-needs-revision
portfolio:infrastructure
portfolio:mixed
portfolio:product
portfolio:unknown
prioritize
prioritized
priority:high
priority:low
priority:medium
propose
ready
retro
retro-proposal
review
stage:backlog
stage:blocked
stage:code
stage:done
stage:intake
stage:merge
stage:propose
stage:review
stage:waiting
status:waiting
tracker
triage
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
agentic-forges/forgejo-mcp#434
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
I would like to run agents in a fairly isolated environment, so it would be nice to be able to limit the available tools to a white list at the configuration level.
I know that it is possible to configure permissions at the Forgejo token level, but in my case it will not be as flexible or convenient.
🤖 Finished Intake · ✅ Success · Started 8:03 AM UTC · Completed 8:03 AM UTC
intake · assessment
verdict:
needs_scope· agent_doable: yes · delivery_path:spec_firstscope: Add a config-level tool allowlist that restricts which MCP tools the server exposes. Proposed bounded first slice: an --enabled-tools flag + FORGEJO_ENABLED_TOOLS env var (comma-separated tool names; empty = all, preserving current behaviour) applied after RegisterTool in operation.Run via ListTools()/DeleteTools(); exact granularity/mechanism to be fixed by the spec_first proposal.
operation/operation.go,cmd/cmd.go,pkg/flag/flag.go,operation/operation_test.gogo test ./...andgo vet ./...pass; README/docs document the new optioncanon: concerns: Config granularity (per-tool name vs domain group) and mechanism (flag/env vs new config file) must be agreed in an OpenSpec proposal before code; Default (no config) must preserve current behaviour — expose all tools — to avoid a silent breaking change
RICE inputs: impact 1 · confidence 0.7 · effort 1 (inputs only — no score; the ranking is the portfolio layer's job)
clarity: overall 0.8
decision needed: At what granularity and via what mechanism should the allowlist work — per individual tool name or per existing domain group (user/repo/issue/...), and configured via a new flag/env var (e.g. --enabled-tools) or a new config file? Allowlist-only, or also a denylist?
why: Feature request: expose a config-level allowlist so an operator can restrict which MCP tools are served, for running agents in an isolated/least-privilege environment (author notes Forgejo token-level permissions exist but are too coarse/inflexible). Grounded in code: operation.Run (operation/operation.go:150-154) unconditionally calls RegisterTool, which registers ~140 tools across 15 fixed domains (cmd/cli.go:45-61); configuration today is only CLI flags + env vars (cmd/cmd.go initFlags/initConfig, pkg/flag/flag.go) with no config file and no filtering seam. Implementation is feasible: mcp-go v0.17.0 exposes ListTools() and DeleteTools(names...), so an allowlist could be applied after RegisterTool in operation.Run, or wired into registration. None of the landing files are harness paths, so it is agent-doable. Not a duplicate (no existing tool-filtering issue/PR found) and not blocked. It is real and worth doing, but the approach is an open design fork — granularity (individual tool name vs the existing domain groups), the config mechanism (new flag/env var vs introducing a config file, which the repo does not yet have), and allow- vs deny-list semantics — where two reasonable engineers would build it differently, so it needs an agreed OpenSpec proposal before code.
Applied (suggest-only): added
stage:intake,needs-scope,Kind/Feature.Recommended next (human): scope the first slice (see above), then apply
propose(spec_first).generated by
castra post-run --persona intake· the agent never mutated this issueThanks — this is a real gap, and agreed on the motivation: token scopes are per-permission, not per-tool, so they can't express "this agent may read issues but never touch webhooks or branch protection".
Before anyone writes code, there are three decisions to settle. Answers here shape the whole surface, so I'd rather agree them now than review them in a PR.
1. Granularity: per tool, or per domain?
The codebase already groups every tool into a domain —
registerToolsWithDomainsincmd/cli.gobuilds atoolDomainsmap over 15 groups (user,repo,issue,pull,search,version,actions,org,tracking,attachment,release,branch-protection,webhook,wiki), and the--clilisting already renders tools grouped by it.That's a strong argument for making domain the primary unit: it's an existing first-class concept rather than a new one, and it's what an operator actually reasons about ("no webhook access"). With ~140 tools, a per-tool allow-list is long, and it silently goes stale — a new tool added upstream is absent from an operator's list, so it is off by default. Whether that default is a feature (fail-closed) or a footgun depends on your view.
My suggestion: domain-level, with per-tool as a refinement later if anyone asks. One caveat —
toolDomainscurrently lives in the--clilayer; the server path would need it hoisted to be usable there. Small, but not zero.2. Mechanism: flag/env, or a config file?
Today configuration is stdlib
flagincmd.initFlagsplus a handful ofos.Getenvreads (FORGEJO_URL,FORGEJO_ACCESS_TOKEN,GITEA_HOST). There is no config file anywhere in the project, so introducing one is a genuine architectural first, not an incremental change — it brings format choice, precedence rules, and a discovery path with it.A comma-separated flag plus matching env var stays inside the existing pattern:
That also matters for how this actually gets deployed — agents are usually launched as a subprocess or a container, where an env var is the natural knob and a config file is another mount to manage.
My suggestion: flag + env now. If a config file lands later for other reasons, this folds into it.
3. Allow-list only, or also a deny-list?
A deny-list is the more ergonomic form for the common case ("everything except
webhookandbranch-protection"), but two mechanisms mean precedence rules and a way to specify both at once.My suggestion: allow-list only to start. It is the fail-closed direction, and it is the one the isolation use case actually needs. Add the deny-list later only if the ergonomics genuinely bite.
Implementation sketch
mcp-go v0.44.0(the version ingo.mod) exposesListTools()andDeleteTools(names ...string)onMCPServer, so filtering after registration works without touching anyRegister*Toolfunction:operation.RuncallsRegisterTool(mcpServer)unconditionally — apply the filter immediately after.Likely files:
operation/operation.go,cmd/cmd.go,cmd/cli.go(hoistingtoolDomains),pkg/flag/flag.go, plus tests.What I need from you
@maks1ms — for your isolation setup, is domain-level granularity enough, or do you specifically need individual tool names? That's the one answer that changes the design rather than just the wording.
Once the three are settled I'll write it up as an OpenSpec proposal before any code.