feat: support stateless per-request authentication for HTTP and SSE transports #138
No reviewers
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!138
Loading…
Reference in a new issue
No description provided.
Delete branch "stateless-token"
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?
Purpose
Currently,
forgejo-mcprelies on a global Forgejo API token provided at startup. This PR introduces stateless, request-scoped authentication by extracting the token from theAuthorizationheader of incoming MCP requests.This enables a single
forgejo-mcpinstance to serve multiple users or agents securely, as each request can carry its own identity.Changes
pkg/forgejo.Client(ctx)to be context-aware. It now prioritizes tokens found in the request context, falling back to the global singleton client if no token is provided (preserving backward compatibility for--stdiomode).StreamableHTTPServerandSSEServerinoperation/operation.goto extracttoken <token>orBearer <token>from the HTTPAuthorizationheader and inject it into the context.context.Contextto the Forgejo client factory.pkg/forgejo/rawhttp.goto use the request-scoped token for binary operations like attachment uploads and downloads.Verification Results
pkg/forgejo/forgejo_test.goto verify correct token extraction and ephemeral client instantiation. All tests passed.stdiomode using the globally configured token.I'm not a go dev (I last wrote go in 2016), but I'm impressed with your ability to respond to my issue quickly. My quick scan of the code indicates that I can get the feature I'm looking for (clients express their token on the Authorization header) by passing in
Bearer <insert token>on theAuthorizationheader), and forgejo-mcp should pass that through to forgejo when it makes its own calls.To clarify the implementation details regarding token extraction from the
Authorizationheader, the current logic supports three formats to ensure maximum compatibility with different MCP clients and Forgejo conventions:Authorization: token <token>: The standard format used by Forgejo/Gitea.Authorization: Bearer <token>: The standard OAuth2 format often used by generic MCP clients and proxies.Authorization: <token>: A minimalist fallback for clients that send the raw token without a prefix (as long as it doesn't contain spaces).The logic in
operation/operation.gohandles this gracefully:This ensures that the server can be used as a drop-in central service for a wide range of client implementations without requiring them to change their authentication headers.
@fsryanorg wrote in https://codeberg.org/goern/forgejo-mcp/pulls/138#issuecomment-15750011:
Ya, gemini can generate go very good. it's in the dna ... and the changes weren't complex. after a check i was brave and opened the pr but @goern and his agents gang may put us more task on the table. ;)
@byteflavour wrote in https://codeberg.org/goern/forgejo-mcp/pulls/138#issuecomment-15750248:
No worries. I appreciate the effort.
@byteflavour this is great work — pulling it down to validate locally now 🚀
one ask before merge: can you drop a showboat demo in this thread? something a reader can copy-paste end-to-end:
forgejo-mcp --transport http --url https://...(no token at startup)curlcalls with differentAuthorizationheaders hitting the sameMcp-Session-Id, showing each one resolves to a different identity viaget_my_user_infobasically the proof-shot from your PR description, but as a runnable script or a copy-paste block. makes the capability undeniable for anyone landing here from issue #137, and it doubles as docs for the new mode.
ideally drop it in the README under a "Multi-tenant HTTP mode" section in the same PR — happy to merge that as part of #138.
op1st Pipelines as Code is skipping this commit.
User byteflavour is not allowed to trigger CI via pull_request_closed in this repo.
oh mhm good morning ... long night ... sorry for the close ... my bad ...
So yes to documentation ... should be a good one ...
op1st Pipelines as Code is skipping this commit.
User byteflavour is not allowed to trigger CI via pull_request in this repo.
op1st Pipelines as Code is skipping this commit.
User byteflavour is not allowed to trigger CI via pull_request in this repo.
@goern Here is the requested showboat demo and updated documentation. I have also added a "Multi-tenant HTTP mode" section to the README and included a new demo file
demos/multi-tenant-http.mdin the latest commit.Showboat Demo: Multi-tenant Stateless Auth
This walkthrough demonstrates how a single
forgejo-mcpinstance can serve multiple identities by reading theAuthorizationheader from incoming requests.1. Start the server (No global token provided)
2. Client Side: Initialize Session
3. Client Side: Request as User A
4. Client Side: Request as User B (same session)
5. Bonus: bogus token
This makes the capability undeniable and well-documented for centralized deployments. Ready for merge! 🚀
op1st Pipelines as Code is skipping this commit.
User byteflavour is not allowed to trigger CI via pull_request in this repo.
@goern I have addressed all the points from your review in the latest commit:
forgejo.Client(ctx)to return(*forgejo.Client, error). If an ephemeral client cannot be created for a request (e.g. invalid token), it now returns an error instead of silently falling back to the global singleton.operation/directory to properly handle client initialization errors.Authorizationheader scheme (tokenorBearer) is now matched case-insensitively.TestClient_AuthorizationHeaderinpkg/forgejo/forgejo_test.gothat uses ahttptest.Serverto verify that concurrent requests with different tokens correctly send their respective tokens in theAuthorizationheader.All tests passed successfully locally (
go test ./...). The PR should now be both secure and robust for multi-tenant use. Ready for another look! 🚀HITL here :D eagerly my test env is already wiped. Give me some minutes to ensure the snowboat demos actually work.
done!
op1st Pipelines as Code is skipping this commit.
User byteflavour is not allowed to trigger CI via pull_request in this repo.
@goern Merge conflicts have been resolved. I have synchronized the branch with the latest
main(including the global style fixes) and manually resolved the conflict inoperation/issue/issue.goto maintain the context-aware client pattern.Build and tests are passing locally. The PR is now mergeable again. 🚀
/ok-to-test
op1st Pipelines as Code/code-scans-d9bw6 is running.
Starting Pipelinerun code-scans-d9bw6 in namespace op1st-pipelines
You can monitor the execution using the op1st Pipelines as Code PipelineRun viewer or through the command line by
using the tkn CLI with the following command:
tkn pr logs -n op1st-pipelines code-scans-d9bw6 -fop1st Pipelines as Code/on-pull-request-ck898 is running.
Starting Pipelinerun on-pull-request-ck898 in namespace op1st-pipelines
You can monitor the execution using the op1st Pipelines as Code PipelineRun viewer or through the command line by
using the tkn CLI with the following command:
tkn pr logs -n op1st-pipelines on-pull-request-ck898 -fop1st Pipelines as Code/code-scans-d9bw6 has failed.
Task Statuses:
fetch-source
gitleaks-version
gitleaks
Failure snippet:
task gitleaks has the status "Failed":op1st Pipelines as Code/on-pull-request-ck898 has successfully validated your commit.
Task Statuses:
fetch-source
build-and-test
op1st Pipelines as Code/code-scans-kp85r is running.
Starting Pipelinerun code-scans-kp85r in namespace op1st-pipelines
You can monitor the execution using the op1st Pipelines as Code PipelineRun viewer or through the command line by
using the tkn CLI with the following command:
tkn pr logs -n op1st-pipelines code-scans-kp85r -fop1st Pipelines as Code/on-pull-request-jws6x is running.
Starting Pipelinerun on-pull-request-jws6x in namespace op1st-pipelines
You can monitor the execution using the op1st Pipelines as Code PipelineRun viewer or through the command line by
using the tkn CLI with the following command:
tkn pr logs -n op1st-pipelines on-pull-request-jws6x -fop1st Pipelines as Code/code-scans-kp85r has successfully validated your commit.
Task Statuses:
fetch-source
gitleaks-version
gitleaks
op1st Pipelines as Code/on-pull-request-jws6x has successfully validated your commit.
Task Statuses:
fetch-source
build-and-test