feat!: full-access Gitea MCP server (1.0.0): full API coverage, stdio + streamable HTTP, admin and per-user OAuth #71
@@ -27,3 +27,72 @@ Every successful tool call returns one text content item with JSON:
|
|||||||
Messages are truncated and stripped of tokens and the Authorization header.
|
Messages are truncated and stripped of tokens and the Authorization header.
|
||||||
- Argument validation failures become `is_error=True` with the jsonschema path.
|
- Argument validation failures become `is_error=True` with the jsonschema path.
|
||||||
- Unknown tool names raise `MCPError(INVALID_PARAMS)`.
|
- Unknown tool names raise `MCPError(INVALID_PARAMS)`.
|
||||||
|
|
||||||
|
## Amendments
|
||||||
|
|
||||||
|
### Secret handling in results (implemented in `gitea/executor.py`, `gitea/encoding.py`)
|
||||||
|
|
||||||
|
Two layers run on every result, success and error, before the size limit:
|
||||||
|
|
||||||
|
1. Exact redaction (always on, independent of `SECRET_DETECTION_MODE`). The
|
||||||
|
following values are replaced with `[REDACTED]` wherever they occur in a
|
||||||
|
string or dict key of the payload: the token of the calling identity,
|
||||||
|
`GITEA_TOKEN`, `MCP_BEARER_TOKEN`, `GITEA_OAUTH_CLIENT_SECRET`, the
|
||||||
|
`extra_secrets` given to the executor, and secret values submitted in the
|
||||||
|
request body of the same call.
|
||||||
|
2. Pattern masking (`SECRET_DETECTION_MODE`). `off` skips it. `mask` replaces
|
||||||
|
each match of the secret patterns (provider keys, JWTs, private key headers,
|
||||||
|
generic `api_key`/`token` assignments) with a shortened form. `block`
|
||||||
|
withholds the whole result and returns an `is_error` result of kind
|
||||||
|
`secret_blocked`.
|
||||||
|
|
||||||
|
Request-body secret fields. Values of these fields, when at least 4 characters
|
||||||
|
long, are collected from the submitted body (nested objects and lists up to a
|
||||||
|
depth of 8) and redacted from the result of that call, because Gitea echoes
|
||||||
|
some of them back (a created webhook returns its `authorization_header`):
|
||||||
|
`password`, `secret`, `client_secret`, `token`, `access_token`, `auth_password`,
|
||||||
|
`auth_token`, `private_key`, `authorization_header`, `remote_password`,
|
||||||
|
`mirror_password`, `mirror_token`, `aws_secret_access_key`, `aws_access_key_id`.
|
||||||
|
For Action secret writes the `data` value is redacted at any length. Only the
|
||||||
|
value sent in the same call is known to the executor, so a later read of an
|
||||||
|
object that stores such a value is not covered by this rule (pattern masking
|
||||||
|
still applies).
|
||||||
|
|
||||||
|
Credential-issuing operations. The successful results of
|
||||||
|
`CREDENTIAL_ISSUING_OPERATIONS` skip pattern masking and block mode, because
|
||||||
|
their whole purpose is to hand the caller a freshly minted credential that
|
||||||
|
masking would make unusable (and block mode would refuse to deliver):
|
||||||
|
|
||||||
|
- `userCreateToken` (access token)
|
||||||
|
- `adminCreateRunnerRegistrationToken`, `orgCreateRunnerRegistrationToken`,
|
||||||
|
`repoCreateRunnerRegistrationToken`, `userCreateRunnerRegistrationToken`
|
||||||
|
(Actions runner registration tokens, delivered in a response header)
|
||||||
|
- `userCreateOAuth2Application`, `userUpdateOAuth2Application` (OAuth2
|
||||||
|
`client_secret`)
|
||||||
|
- `repoCreateDeployToken` (deploy token)
|
||||||
|
|
||||||
|
Exact redaction still applies to these results, so the caller's own tokens are
|
||||||
|
never echoed, and their errors are masked like every other error. Operators who
|
||||||
|
do not want any credential to pass through the model context should disable
|
||||||
|
these tools with `DISABLED_TOOLS`.
|
||||||
|
|
||||||
|
Hardening of the result path: JSON from Gitea nested deeper than 100 levels is
|
||||||
|
returned as text instead of being parsed (the recursive redaction and masking
|
||||||
|
passes must not hit the interpreter recursion limit), and a base64 file upload
|
||||||
|
whose decoded size would exceed `MAX_BINARY_BYTES` is rejected before it is
|
||||||
|
decoded with kind `payload_too_large`.
|
||||||
|
|
||||||
|
### Path encoding and the raw tool
|
||||||
|
|
||||||
|
Path values are percent-encoded with `/` escaped as `%2F`, except for the
|
||||||
|
parameters that Gitea 28.1.0 serves through wildcard routes (branch, tag, file
|
||||||
|
path, ref and compare names on `branches`, `tags`, `contents`, `contents-ext`,
|
||||||
|
`raw`, `media`, `compare`, `git/refs`, `commits/{ref}/status(es)` and
|
||||||
|
`pulls/{base}/{head}`). The decision is keyed on the route and the parameter,
|
||||||
|
not the parameter name: `releases/tags/{tag}` and `editorconfig/{filepath}` are
|
||||||
|
single-segment routes that only resolve with `%2F`.
|
||||||
|
|
||||||
|
The raw tool `gitea_api_request` rejects `%` in paths, so it cannot send
|
||||||
|
`%2F`. A route with a single-segment parameter whose value contains a slash
|
||||||
|
(a release tag such as `rel/1.0`) must be called through the generated tool for
|
||||||
|
that operation.
|
||||||
Reference in new issue
Block a user