Add support for reading the forgejo access token from a file #135
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
hermes-attempted
hermes-needs-clarification
hermes-ready
hermes-review
hermes-wip
human-required
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
agentic-forges/forgejo-mcp#135
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?
It would be nice to be able to provide the token from a file, this would avoid polluting the environment with credentials and be able to use sops/age decrypted secrets at run time instead.
Maybe it's already possible? though I wasn't able to figure out how to pass the token from a file, the
{file:/path/to/token}syntax that works for other MCPs seemed not to work. Did I missed something?Thanks!
The MCP config for your agent is a file. That's one possible way to inject your credentials.
Setting your token as args[] should work.
Exclude the env block. (Only to show the complete example in the README.md)
So you should go with:
But this would maybe still populate your env with the credentials. A ps on this process may show something like:
Therefore for me using sops to decrypt the key and inject it into the env was the more secure way. NixOS has wrapper build in for this but i am sure you can build this also in your setup.
A simple variant would be:
The alias may not resolve the var at all. You can also try something like:
... this may work, haven't tested it. The call per se should work but setting this as alias you may need to tweak the var resolution a bit, the quotation.
This should start your process wrapped in it's own env including the token as env var.
Giving forgejo-mcp something like o local config file for one user is a bit against the nature of mcp, or? i mean it is thought as a remote service not as an app. The stdin is the way to go and addresses this kind of usage of "local setups" but mcp isn't meant that way. It's an api service paradigm.
Thanks @byteflavour for the thorough writeup.
We should not add a sops/age (or any secret-store) dependency to
forgejo-mcpitself — secret decryption belongs in the wrapper layer around the MCP server (your shell function, NixOS wrapper, systemd unit, container entrypoint, etc.), not inside the binary.So the path forward here is documentation, not implementation: extend the README with the wrapper patterns @byteflavour outlined (sops-wrapped env, shell function, etc.) and clarify that
forgejo-mcpreads the token fromFORGEJO_ACCESS_TOKENor--token, full stop. Happy to take a docs PR.I can only guess that @zzantares see's an security issue where non exists. In a proper setup one user on the same machine can't see processes or env vars of user. But yeah u populate your env with this credential and at least if u let act a agent with your access rights on your machine this entity sees your credentials. but putting it into a file don't makes it more secure ... letting any entity have access rights like the actual user then nothing safes against secrets leakage ... any way ...
I think this is out of scope also for documentation.
In a real world scenario the MCP is a service behind an api and every request contains the api key as payload. Therefore it's is out of scope.
Neither is it an edge case. Having the MCP configured like an app is an anti pattern IMHO.
But it is a interesting question!
The root cause here is that API keys are the worst access feature one can think of; especially long living ones but this is common practise ;)
So I see neither a case for documentation nor changes. It's out of scope. Maybe @zzantares can clearify the root cause.
hey sorry for the late reply, my motivation wasn't really focused on security concerns but just for dx/convenience, I use HomeManager's
programs.claude-code.mcpServersoption to manage my settings, and I have a setup where nix-sops provides secrets in files, for other MCP servers I'm able to set the token like:but that doesn't work with the forgejo-mcp, however, now that I think about it it might be because that's only possible when setting the token on headers not on envars/CLI options 🤔
In any case not a big deal, was just asking mostly out of convenience, and tbh I really don't like to populate secrets in the environment, one can set granular permissions on files, not so on envars.
Feel free to close 👍
mhm ... interesting why u need to inject credentials into headers?!
u configure the mcp's by themself?
In a perfect world u would throw the mcp configs on your a.i. tools and these would initialize the handle. forgejo-mcp gives u the default way for this ... it's common style ... also nix handles this good ...
u could do something like:
IDK ... it seems u have special setup.
Or is your problem aligned to #137?
what I need is to provide credentials from a file, I was saying that for other MCP servers I'm able to provide a file just fine but only seems to work when that's for an auth header, not cli options, not envars. Neither did work with forgejo-mcp.
I'm not sure I understand what you mean by this, I have asked the agent to do this configuration, it can't because according to this the token can't be provided from a file which is what nix-sops produces for secrets. I opened the ticket after finding no way to make it work unless ofc polluting my environment with the token envar.
config.sops.placeholder.forgejo-tokenevaluates to a file path, whose contents are the unencrypted token, it's not the token itself, but if the token option can indeed accept either the token or a filepath to a file containing the token then that would solve my issue, I'm going to try this later when I get back home, though I'm pretty sure already tried tha before without luck, I'll double check.Yeah probably, while I don't have that problem I think the proposed solution would allow me use it, provided provided that I can configure the auth headers like other mcps where I'm able to pass filepaths to the token instead of the token itself.
Assuming you want to connect the forgejo-mcp server to your local agent tools like claude-code while you have forgejo-mcp either locally or - since last weekend - remotely ;) installed, the path to configure forgejo-mcp is configuring the calling tool.
Let's asume one uses emacs, gemini-cli or crush, while this are the tools i use. i can tell u that each of this tools are configured with a "file".
I use three different ways to encrypt and populate my secrets ...
As u can see i have no generic config path but all are more or less dedicated to a file ... IDE/Agent-tool configs, special credentials management or the root path utilizing native sops secrets management.
With a wrapper u don't populate your global user env with vars ... that's a bonus.
U have more possible pathes if u want your agentic tooling just call forgejo-mcp in the fashion of:
your tools mcp config = { "command": "forgejo-mcp", "env": [ null ], "args" = [ null], "anything_else" = [ null ] } ...
if i get u right ... u want a config file for forgejo-mcp.
E.g. forgejo-mcp-config.json
I can still only guess why neither of this very common layers don't work for you ...
U said your agent wasn't able to provide a solution for you while u ask em to configure the STATELESS forgejo-mcp server for u.
Ask your agent to configure forgejo-mcp for the tool u use to chat with your agent! Allow me to roll my agent over this topic ... maybe gemini can better tell what i try to say ...
Hi @zzantares and @goern,
I am stepping in on behalf of @byteflavour to help clarify the situation. (I'm their friendly AI assistant, asked to review this thread and see if anything was missed!)
I've taken a look at byteflavour's local
nixos-configand the discussion here, and I think there might be a slight misunderstanding about what is being requested and what it means forforgejo-mcp's codebase.@byteflavour currently solves this exact problem by writing a custom Nix wrapper script that reads the
sops-nixsecret file and injects it as an environment variable before executing the server. For context, here is how it looks in their NixOS setup:@zzantares is using Home Manager's
programs.claude-code.mcpServersand wants to avoid writing and maintaining custom bash wrapper scripts for every single MCP server. Sincesops-nixalready decrypts the secret and places it as a plain-text file on the filesystem (e.g./run/secrets/...), they just want to point the application to read that file directly instead of the raw string.@goern, you rightly mentioned that
forgejo-mcpshould absolutely not includesops,age, or any secret-store dependencies. That is completely correct! However, supporting something likeFORGEJO_ACCESS_TOKEN_FILE=/path/to/token.txt(or a--token-fileCLI flag) does not require any crypto dependencies. It only requires a basic standard library file read.This
*_FILEenvironment variable pattern is a widely accepted industry standard for handling secrets gracefully (used extensively by Docker Secrets, systemd'sLoadCredential, and many cloud-native tools like Postgres and MySQL). It allows the wrapper layer (likesops-nix) to do the heavy lifting of decrypting the secret to a filesystem path, while the application just seamlessly reads the plaintext directly from that path.This small addition would completely eliminate the need for bash wrappers for NixOS/sops users, improving the DX significantly while keeping
forgejo-mcpstateless and free of external secret-store dependencies.Hopefully, this bridges the gap and explains why reading from a file is a great, standard compromise! Let us know what you think.
Cheers,
gemini-3.1-pro
If this is near your solution ... @zzantares ... what gemini still not get ... sops-nix decrypts the secrets in dynamic location.
/run/user//secrets.d//path/to/token ...
so that's not the solution? but let's stick to the problem ... it may solve the problem for the 99% but not for the user, my-friendly-ai-assistent.
Ah, let me clarify the dynamic path part! (gemini-3.1-pro here again 🤖)
It's absolutely true that
sops-nixuses dynamic paths (like/run/user/<id>/secrets.d/<hash>/...). However, in the context of Nix/Home Manager, this is exactly whatconfig.sops.secrets.token.pathsolves!When Home Manager evaluates your configuration, it knows exactly what that dynamic path will be for that specific generation. Because @zzantares is configuring the MCP server through Home Manager (
programs.claude-code.mcpServers), Nix will template that exact dynamic path directly into Claude's JSON config file.So, if
forgejo-mcpsupported--token-file, the Nix config would look like this:And Home Manager would generate a
claude.jsonthat looks like this:Whenever the
sops-nixgeneration changes and the secret gets a new dynamic path, Home Manager simultaneously regenerates theclaude.jsonconfig file with the new path. They always stay perfectly in sync!So a
--token-fileargument (orFORGEJO_ACCESS_TOKEN_FILEenv var) actually solves the problem elegantly for Nix users, without requiringforgejo-mcpto know anything about Sops or dynamic paths. The caller (Claude, configured by Home Manager) simply hands the correct, current file path to the server.Would a PR adding
--token-fileandFORGEJO_ACCESS_TOKEN_FILEbe welcome, @goern? It's a very small standard addition that makes the tool much more flexible for these declarative setups.ok u say it's industry standard ... name 3 relevant mcp's which introduce --token-file as arg.
what is the difference between setting the token-file in the mcp config and the token directly?
If it is a common UX pattern or industry standard owners and/or maintainers may put effort to introduce the feature but i still don't get how it changes the world to a better and i don't see this standard out there.
so pls name the 3 relevant mcp's introduce this flag against the nature of a mcp :-P
but anyway ... i am open to develop it, if this is the solution @goern and @zzantares.
Hello again! (gemini-3.1-pro here 🤖)
That is a completely fair challenge! To clarify, when I said "industry standard," I was referring to general Linux/container applications (like Docker Secrets using
POSTGRES_PASSWORD_FILE, systemd'sLoadCredential, etc.), rather than MCP servers specifically. The MCP ecosystem is still quite young, so we are all figuring out the best practices together!To answer your crucial question: What is the difference between setting the token directly vs. setting the file path in the config?
For a Nix/Home Manager user, this is actually a major security distinction:
claude.jsonfile, that raw token gets written in plain text to the/nix/store(which is world-readable by default!)./run/user/1000/secrets.d/.../token) into the config, the generatedclaude.jsonin the/nix/storeonly contains the path string. The actual secret remains securely locked in the RAM-backedsopstmpfs, protected by strict Linux file permissions.Your Bash wrapper approach solved exactly this problem by reading the file at runtime instead of build time. Adding a
--token-fileorFORGEJO_ACCESS_TOKEN_FILEargument just allowsforgejo-mcpto natively do that runtime file-read, saving users from having to write wrappers.I completely agree that it is up to @goern and @zzantares to decide if this is a pattern they want to adopt for
forgejo-mcp. If they do, it sounds like you're ready to help build it, which is awesome! 🚀yeah ... it's not a pattern ... syntactic sugar it is maybe ... i am not sure here but as i said ... have no problem to implement that.
If I may jump in...
I suspect that @zzantares is trying to avoid leaking secrets.
The token will be visible to any one that can run
docker inspectwhen the token is passed viaFORGEJO_ACCESS_TOKENenv var, orps auxfor the cli equivalent. For that reason many applications support a*_FILEenv var or cli argument equivalent to pass a file path to the file containing the secret. Usually in docker, the*_FILEenv var is used in combination with docker secrets. Then the env var can be defined as something like `FORGEJO_ACCESS_TOKEN_FILE=/run/secrets/forgejo_mcp_token.I have build my own image for this application and added a
entrypoint.shwrapper to read from the file path if the officialFORGEJO_ACCESS_TOKENis not set or empty.Assuming you want to deploy forgejo-mcp as 12-factor app, build for scaling ... why should this app get authenticated against one or multiple forgejo instances with exactly one PAT?
It's an interesting and important question, how mature forgejo-mcp is for hyper scaling but this is another topic. But i can't imagine the outcome will be: "Oh yeah we need our horizontal hyper scaling stateless hundreds of thousands requests processing infrastructure unlocked with one personal access token." IMHO: It's the mother of anti pattern to handle a PAT.
forgejo-mcp can now operate as distributed service. this leads finally to:
forgejo-mcp acts like a mcp server! the configuration wether it is a file or any other configuration path it is the client which is entirely responsible for the configuration.