# Reported issues for transkribus

Pod holds 16 of 17 problems reported by people outside the maintainer team. Issues filed by the project's own owners, members and collaborators are excluded entirely — a maintainer's release checklist is not a warning to a prospective user.

Back to [transkribus](/mcp/transkribus).

## Most discussed

### Port the hardened Retry-After parser from lexware #54 + first real interceptor test coverage

Same fleet bug-class lexware fixed in lazyants/lexware-mcp-server#54; sibling issue exists for hetzner. The 429/401 interceptor currently has zero direct test coverage.

## Findings

### Transkribus 429 backoff has the same bare-parseInt Retry-After bug lexware fixed in #54

`transkribus-mcp-server/src/services/transkribus.ts:105` — **low** bug

**Evidence:** transkribus.ts:102-107: `const retryAfter = error.response.headers['retry-after']; let delay: number; if (retryAfter) { delay = parseInt(r

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/31) · 2026-07-03 · open · 2 comments

### Port parseRetryAfterMs: 429 Retry-After HTTP-date parses to NaN -> immediate retry

Same bug class as lexware #54 and hetzner #61 — verified still present here.

`src/services/transkribus.ts:102`:
```ts
delay = parseInt(retryAfter, 10) * 1000;
```
`parseInt` yields `NaN` for the RFC 7231 HTTP-date form of `Retry-After` (only bare delta-seconds parse), so `setTimeout(NaN)` fires immediately and collapses the 429 backoff into a tight retry loop against an already rate-limited API.

### Fix (port the shipped fleet solution)
hetzner-mcp-server fixed this in **#61 (PR #65)** by port

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/41) · 2026-07-18 · closed · 1 comment

### Accepted #26 redaction residuals (adversarial-only, defense-in-depth)

Tracking, low priority. The #26 fail-closed sanitizer (PR #37) covers all realistic JSON responses. Two residuals remain, each requiring an **adversarial / hand-crafted non-JSON** server response and documented in code as accepted:

1. A bare session token echoed in a **non-Set-Cookie custom header** with no `sessionid=` key prefix and not equal to the module session — not collected, survives on the chained cause.
2. A token split across **non-session-named keys** (e.g. `{JSESSION:{ID:x}}`) — no

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/40) · 2026-07-17 · closed · 1 comment

### Tighten models.ts `type` path segment to a verified z.enum

Deferred from #32 (PR #37). The 14 model tools type `type` as `PathSegmentSchema` (a string guard + pathSeg encoding). The API documents a closed set (e.g. `htr`/`la`/`ocr`) but the authoritative value list is unverified. A `z.enum` would make traversal unreachable by construction — stronger than pathSeg.

**Blocker:** needs the authoritative `type` value set from the Transkribus API — an incomplete enum would break working calls (a hard regression). Verify against the API, then convert the 14 s

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/38) · 2026-07-17 · closed · 1 comment

### 429 backoff mis-parses Retry-After HTTP-date to NaN → immediate retry

Same class as lexware #54 (fixed there with `parseRetryAfterMs`, shipped in lexware 4.0.0). `src/services/transkribus.ts:107` computes the 429 delay with `parseInt(retryAfter, 10) * 1000` (header read at `:104`), which is `NaN` for an HTTP-date `Retry-After`; `setTimeout(NaN)` fires immediately and defeats the backoff.

**Fix:** port lexware's `parseRetryAfterMs` (delta-seconds + strict IMF-fixdate validated by an exact `Date.UTC` round-trip) to `:107`, and add the accompanying `retry-after.test

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/36) · 2026-07-17 · closed · 1 comment

### Schema consistency: 18 hand-rolled pagination blocks (losing intCoerce), raw ID params in du.ts/kws.ts, hand-rolled userid params

## Findings

### 17 tools hand-roll index/nValues/sortColumn/sortDirection instead of spreading PaginationParams, silently losing intCoerce string coercion

`transkribus-mcp-server/src/tools/collections-core.ts:19` — **medium** duplication

**Evidence:** collections-core.ts:19-22 `index: z.number().int().optional().default(0)... nValues: z.number().int().optional().default(0)... sortColumn... sortDirection...` — the same 4-line block is copy-pasted in 17 tools (collections-core ×4, collections-u

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/33) · 2026-07-03 · open · 1 comment

### Add pathSeg(): raw string params interpolated into URL paths (admin tools + 14 model tools); scrub config.params too

The repo has zero encodeURIComponent usage. Hetzner already ships `pathSeg()` in schemas/common.ts after codex flagged the same class on its DNS PR. The config.params scrub extension complements #26 (which covers bodies).

## Findings

### Admin tool string params (reportType, reportTime, jobImpl) interpolated raw into URL path without encoding

`transkribus-mcp-server/src/tools/admin.ts:103` — **low** security

**Evidence:** reportType: z.string() / reportTime: z.string() then: return transkrib

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/32) · 2026-07-03 · closed · 1 comment

### 401 re-auth interceptor recurses without bound when /auth/login itself responds 401

## Findings

### 401 interceptor recurses without bound when /auth/login itself responds 401

`transkribus-mcp-server/src/services/transkribus.ts:86` — **medium** bug

**Evidence:** `if (error.response?.status === 401) { ... if (!retried) { (config...).__authRetried = true; sessionId = await login(client); ... } }` — login() posts /auth/login through the SAME client, so a 401 from the login endpoint re-enters this handler with a FRESH config (no __authRetried), which calls login() again, recursi

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/30) · 2026-07-03 · closed · 1 comment

## Most recent

### Port lexware's OS-keyring credential resolution (env var as fallback)

`lexware-mcp-server` 4.2.0 moved credential lookup off plain-text env vars and onto the OS keyring, with the env var kept as a fallback (lazyants/lexware-mcp-server#91, commit `eb2a89c`). This server still reads its credentials only from the environment, so a Claude Desktop / Claude Code config file has to carry the password in clear text.

## Current state

`src/services/transkribus.ts:9-16` reads `TRANSKRIBUS_USER`, `TRANSKRIBUS_PASSWORD` and `TRANSKRIBUS_SESSION_ID` directly from `process.env

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/44) · 2026-07-27 · open · 0 comments

### ensureSession() has no in-flight login de-duplication

`ensureSession()` in `src/services/transkribus.ts` has no promise memo, so N concurrent cold-start tool calls can each fire a `/auth/login`. Pre-existing; low impact now that #30 (PR #37) removed the recursion, but concurrent first-calls still mean redundant logins.

**Fix:** memoize the in-flight login promise (store the pending `login()` promise, await it for concurrent callers, clear on settle). Noted while fixing #30.

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/39) · 2026-07-17 · open · 0 comments

### Feature ideas from audit: job_wait, doc_get_plaintext, export download URL, page images as MCP image content

Curated from the audit's gap analysis. Complements (does not depend on) the deferred Metagrapho work in #22.

## Proposals

- **transkribus_job_wait — poll a job until FINISHED/FAILED with timeout** _(small)_ — Every substantive action (HTR/OCR/LA recognition, training, export, doc duplication, ingest) returns a jobId and completes asynchronously; today an LLM client must call transkribus_job_get in a manual loop, burning turns and context on 'still RUNNING' JSON. A single tool that polls GET /j

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/35) · 2026-07-03 · open · 0 comments

### Cleanup & test hardening: dead src/types/, smoke-test entry lists, redaction-test extension

## Findings

### src/types/ directory (7 files, 142 lines) is dead code — zero imports anywhere in the repo

`transkribus-mcp-server/src/types/common.ts:1` — **low** refactor

**Evidence:** `export interface TranskribusSession { sessionId: string; userId: number; ... }` — grep for `types/<name>.js` and any `../types/` import across src/ returns nothing outside src/types/ itself. All 7 modules (collections, common, credits, jobs, models, recognition, user) are unreferenced; tools type responses a

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/34) · 2026-07-03 · open · 0 comments

### transkribus_auth_login sends username/password/OTP as URL query parameters

Flagged independently by both the security and the bugs finder: credentials land in server/proxy logs AND the endpoint (JAX-RS @FormParam) won't read them from the query string, so the tool both leaks and fails. The safe form-encoded `login()` already exists in services/transkribus.ts.

## Findings

### transkribus_auth_login sends username/password/OTP as URL query params instead of form body

`transkribus-mcp-server/src/tools/auth.ts:52` — **medium** bug

**Evidence:** Tool: `transkribusReques

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/29) · 2026-07-03 · closed · 0 comments

### Document ingestion is broken end-to-end: 8 tools send JSON where the API expects multipart/XML/CSV/query params

Every ingestion path was verified against the live TrpServer WADL / official Java client: as shipped there is **no working way to get a document into Transkribus** through this server. The multipart helper `transkribusUpload()` already exists but is dead code. Decision needed per tool: implement real transport or remove the tool (a tool that can never succeed is worse than a missing one).

## Findings

### transkribus_upload_page cannot upload a page image — sends JSON to a multipart/form-data e

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/28) · 2026-07-03 · open · 1 comment

### Redact secrets from AxiosError request/response BODIES (config.data password + response.data session echo)

Follow-up to #23 / PR #25. The cookie sanitizer (`sanitizeAxiosError`) added in #23 strips secrets from the **cause-chain structure** — request `Cookie` / response `Set-Cookie` / `authorization` / `proxy-authorization` headers, `config.auth` / `proxy.auth`, the raw `request._header` block, and an object `cause`. It deliberately does **not** touch request/response **bodies**, to preserve request-body debuggability for normal requests. Two distinct body-secret vectors remain:

### 1. Login passwor

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/26) · 2026-06-22 · closed · 1 comment

### Harden wrapAxiosError: strip request._header + cause; assert depth:null & toJSON leak-free

`src/services/transkribus.ts` `wrapAxiosError` chains `{ cause: err }` and is documented as safe for **default-depth** `util.inspect` — but it does **not** actively strip secrets, so it's below the fleet strong-sanitizer bar now set by lexware #51 (`sanitizeAxiosError`, released v3.2.1) and hetzner #44. The secret here is the **session cookie** `JSESSIONID`, not a bearer token: the request interceptor sets `config.headers['Cookie'] = 'JSESSIONID=' + sessionId`, so on a thrown `AxiosError` that c

[Read the thread](https://github.com/lazyants/transkribus-mcp-server/issues/23) · 2026-06-20 · closed · 0 comments

The remaining reports are on [the project's issue tracker](https://github.com/lazyants/transkribus-mcp-server/issues).
