🛂 fix: Encode Non-ASCII Characters in MCP Server Headers (#11432)

Fixes ByteString conversion errors when user names contain Unicode
characters > 255 (e.g., ć, đ, ł, š, ž) in MCP server headers.

- Add encodeHeaderValue() function to Base64 encode extended Unicode
- Update processUserPlaceholders() to encode name/username/email in headers
- Update processSingleValue() with isHeader parameter
- Apply encoding in processMCPEnv() and resolveHeaders()

Tested locally with MCP server using user name 'Đorđe' (contains đ=272).
Headers are correctly encoded as base64, preventing ByteString errors.

Co-authored-by: kenzaelk98 <kenzaelk98@leoninestudios.com>
Co-authored-by: heptapod <164861708+leondape@users.noreply.github.com>
This commit is contained in:
kenzaelk98 2026-01-21 20:00:25 +01:00 committed by GitHub
parent 11210d8b98
commit 191cd3983c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 165 additions and 13 deletions

View file

@ -1,5 +1,10 @@
import { TokenExchangeMethodEnum } from 'librechat-data-provider';
import { resolveHeaders, resolveNestedObject, processMCPEnv } from './env';
import {
resolveHeaders,
resolveNestedObject,
processMCPEnv,
encodeHeaderValue,
} from './env';
import type { MCPOptions } from 'librechat-data-provider';
import type { IUser } from '@librechat/data-schemas';
import { Types } from 'mongoose';
@ -32,6 +37,83 @@ function createTestUser(overrides: Partial<IUser> = {}): IUser {
} as IUser;
}
describe('encodeHeaderValue', () => {
it('should return empty string for empty input', () => {
expect(encodeHeaderValue('')).toBe('');
});
it('should return empty string for null/undefined coerced to empty string', () => {
// TypeScript would prevent these, but testing runtime behavior
expect(encodeHeaderValue(null as any)).toBe('');
expect(encodeHeaderValue(undefined as any)).toBe('');
});
it('should return empty string for non-string values', () => {
expect(encodeHeaderValue(123 as any)).toBe('');
expect(encodeHeaderValue(false as any)).toBe('');
expect(encodeHeaderValue({} as any)).toBe('');
});
it('should pass through ASCII characters (0-127) unchanged', () => {
expect(encodeHeaderValue('Hello')).toBe('Hello');
expect(encodeHeaderValue('test@example.com')).toBe('test@example.com');
expect(encodeHeaderValue('ABC123')).toBe('ABC123');
});
it('should pass through Latin-1 characters (128-255) unchanged', () => {
// Characters with Unicode values 128-255 are safe
expect(encodeHeaderValue('José')).toBe('José'); // é = U+00E9 (233)
expect(encodeHeaderValue('Müller')).toBe('Müller'); // ü = U+00FC (252)
expect(encodeHeaderValue('Zoë')).toBe('Zoë'); // ë = U+00EB (235)
expect(encodeHeaderValue('Björk')).toBe('Björk'); // ö = U+00F6 (246)
});
it('should Base64 encode Slavic characters (>255)', () => {
// Slavic characters that cause ByteString errors
expect(encodeHeaderValue('Marić')).toBe('b64:TWFyacSH'); // ć = U+0107 (263)
expect(encodeHeaderValue('Đorđe')).toBe('b64:xJBvcsSRZQ=='); // Đ = U+0110 (272), đ = U+0111 (273)
});
it('should Base64 encode Polish characters (>255)', () => {
expect(encodeHeaderValue('Łukasz')).toBe('b64:xYF1a2Fzeg=='); // Ł = U+0141 (321)
});
it('should Base64 encode various extended Unicode characters (>255)', () => {
expect(encodeHeaderValue('Žarko')).toBe('b64:xb1hcmtv'); // Ž = U+017D (381)
expect(encodeHeaderValue('Šime')).toBe('b64:xaBpbWU='); // Š = U+0160 (352)
});
it('should have correct b64: prefix format', () => {
const result = encodeHeaderValue('Ćiro'); // Ć = U+0106 (262)
expect(result.startsWith('b64:')).toBe(true);
// Verify the encoded part after prefix is valid Base64
const base64Part = result.slice(4);
expect(Buffer.from(base64Part, 'base64').toString('utf8')).toBe('Ćiro');
});
it('should handle mixed safe and unsafe characters', () => {
const result = encodeHeaderValue('Hello Đorđe!');
expect(result).toBe('b64:SGVsbG8gxJBvcsSRZSE=');
});
it('should be reversible with Base64 decode', () => {
const original = 'Marko Marić';
const encoded = encodeHeaderValue(original);
expect(encoded.startsWith('b64:')).toBe(true);
// Verify decoding works
const decoded = Buffer.from(encoded.slice(4), 'base64').toString('utf8');
expect(decoded).toBe(original);
});
it('should handle emoji and other high Unicode characters', () => {
const result = encodeHeaderValue('Hello 👋');
expect(result.startsWith('b64:')).toBe(true);
const decoded = Buffer.from(result.slice(4), 'base64').toString('utf8');
expect(decoded).toBe('Hello 👋');
});
});
describe('resolveHeaders', () => {
beforeEach(() => {
process.env.TEST_API_KEY = 'test-api-key-value';