Skip to content

Commit 7b97fce

Browse files
authored
Merge pull request #3 from dkmaker/add-headers
feat: add custom header support
2 parents b36771f + 9a48e0d commit 7b97fce

File tree

4 files changed

+162
-46
lines changed

4 files changed

+162
-46
lines changed

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@ Add to `C:\Users\<YourUsername>\AppData\Roaming\Code\User\globalStorage\saoudriz
100100
// SSL Verification (enabled by default)
101101
"REST_ENABLE_SSL_VERIFY": "false", // Set to false to disable SSL verification for self-signed certificates
102102
// Response Size Limit (optional, defaults to 10000 bytes)
103-
"REST_RESPONSE_SIZE_LIMIT": "10000" // Maximum response size in bytes
103+
"REST_RESPONSE_SIZE_LIMIT": "10000", // Maximum response size in bytes
104+
// Custom Headers (optional)
105+
"HEADER_X-API-Version": "2.0",
106+
"HEADER_Custom-Client": "my-client",
107+
"HEADER_Accept": "application/json"
104108
}
105109
}
106110
}
@@ -129,7 +133,11 @@ Add to `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude
129133
"AUTH_APIKEY_HEADER_NAME": "X-API-Key",
130134
"AUTH_APIKEY_VALUE": "your-api-key",
131135
// SSL Verification (enabled by default)
132-
"REST_ENABLE_SSL_VERIFY": "false" // Set to false to disable SSL verification for self-signed certificates
136+
"REST_ENABLE_SSL_VERIFY": "false", // Set to false to disable SSL verification for self-signed certificates
137+
// Custom Headers (optional)
138+
"HEADER_X-API-Version": "2.0",
139+
"HEADER_Custom-Client": "my-client",
140+
"HEADER_Accept": "application/json"
133141
}
134142
}
135143
}
@@ -146,7 +154,11 @@ Note: Replace the environment variables with your actual values. Only configure
146154
- Test REST API endpoints with different HTTP methods
147155
- Support for GET, POST, PUT, and DELETE requests
148156
- Detailed response information including status, headers, and body
149-
- Custom header support
157+
- Custom Headers:
158+
- Global headers via HEADER_* environment variables
159+
- Case-insensitive prefix (HEADER_, header_, HeAdEr_)
160+
- Case preservation for header names
161+
- Priority-based application (per-request > auth > custom)
150162
- Request body handling for POST/PUT methods
151163
- Response Size Management:
152164
- Automatic response size limiting (default: 10KB/10000 bytes)

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

Lines changed: 123 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,54 @@ interface ValidationResult {
4949
};
5050
}
5151

52+
// Function to sanitize headers by removing sensitive values and non-approved headers
53+
const sanitizeHeaders = (
54+
headers: Record<string, any>,
55+
isFromOptionalParams: boolean = false
56+
): Record<string, any> => {
57+
const sanitized: Record<string, any> = {};
58+
59+
for (const [key, value] of Object.entries(headers)) {
60+
const lowerKey = key.toLowerCase();
61+
62+
// Always include headers from optional parameters
63+
if (isFromOptionalParams) {
64+
sanitized[key] = value;
65+
continue;
66+
}
67+
68+
// Handle authentication headers
69+
if (
70+
lowerKey === 'authorization' ||
71+
(AUTH_APIKEY_HEADER_NAME && lowerKey === AUTH_APIKEY_HEADER_NAME.toLowerCase())
72+
) {
73+
sanitized[key] = '[REDACTED]';
74+
continue;
75+
}
76+
77+
// For headers from config/env
78+
const customHeaders = getCustomHeaders();
79+
if (key in customHeaders) {
80+
// Show value only for headers that are in the approved list
81+
const safeHeaders = new Set([
82+
'accept',
83+
'accept-language',
84+
'content-type',
85+
'user-agent',
86+
'cache-control',
87+
'if-match',
88+
'if-none-match',
89+
'if-modified-since',
90+
'if-unmodified-since'
91+
]);
92+
const lowerKey = key.toLowerCase();
93+
sanitized[key] = safeHeaders.has(lowerKey) ? value : '[REDACTED]';
94+
}
95+
}
96+
97+
return sanitized;
98+
};
99+
52100
interface ResponseObject {
53101
request: {
54102
url: string;
@@ -67,11 +115,13 @@ interface ResponseObject {
67115
validation: ValidationResult;
68116
}
69117

118+
const normalizeBaseUrl = (url: string): string => url.replace(/\/+$/, '');
119+
70120
const isValidEndpointArgs = (args: any): args is EndpointArgs => {
71121
if (typeof args !== 'object' || args === null) return false;
72122
if (!['GET', 'POST', 'PUT', 'DELETE'].includes(args.method)) return false;
73123
if (typeof args.endpoint !== 'string') return false;
74-
if (args.headers !== undefined && typeof args.headers !== 'object') return false;
124+
if (args.headers !== undefined && (typeof args.headers !== 'object' || args.headers === null)) return false;
75125

76126
// Check if endpoint contains a full URL
77127
const urlPattern = /^(https?:\/\/|www\.)/i;
@@ -91,6 +141,22 @@ const hasBasicAuth = () => AUTH_BASIC_USERNAME && AUTH_BASIC_PASSWORD;
91141
const hasBearerAuth = () => !!AUTH_BEARER;
92142
const hasApiKeyAuth = () => AUTH_APIKEY_HEADER_NAME && AUTH_APIKEY_VALUE;
93143

144+
// Collect custom headers from environment variables
145+
const getCustomHeaders = (): Record<string, string> => {
146+
const headers: Record<string, string> = {};
147+
const headerPrefix = /^header_/i; // Case-insensitive match for 'header_'
148+
149+
for (const [key, value] of Object.entries(process.env)) {
150+
if (headerPrefix.test(key) && value !== undefined) {
151+
// Extract header name after the prefix, preserving case
152+
const headerName = key.replace(headerPrefix, '');
153+
headers[headerName] = value;
154+
}
155+
}
156+
157+
return headers;
158+
};
159+
94160
class RestTester {
95161
private server!: Server;
96162
private axiosInstance!: AxiosInstance;
@@ -115,7 +181,7 @@ class RestTester {
115181

116182
const https = await import('https');
117183
this.axiosInstance = axios.create({
118-
baseURL: process.env.REST_BASE_URL,
184+
baseURL: normalizeBaseUrl(process.env.REST_BASE_URL!),
119185
validateStatus: () => true, // Allow any status code
120186
httpsAgent: REST_ENABLE_SSL_VERIFY ? undefined : new https.Agent({ // Disable SSL verification only when explicitly set to false
121187
rejectUnauthorized: false
@@ -202,42 +268,42 @@ class RestTester {
202268
tools: [
203269
{
204270
name: 'test_request',
205-
description: `Test a REST API endpoint and get detailed response information.
206-
207-
Base URL: ${process.env.REST_BASE_URL}
208-
209-
SSL Verification: ${REST_ENABLE_SSL_VERIFY ? 'Enabled' : 'Disabled'} (see config resource for SSL settings)
210-
211-
Authentication: ${
271+
description: `Test a REST API endpoint and get detailed response information. Base URL: ${normalizeBaseUrl(process.env.REST_BASE_URL!)} | SSL Verification ${REST_ENABLE_SSL_VERIFY ? 'enabled' : 'disabled'} (see config resource for SSL settings) | Authentication: ${
212272
hasBasicAuth() ?
213273
`Basic Auth with username: ${AUTH_BASIC_USERNAME}` :
214274
hasBearerAuth() ?
215275
'Bearer token authentication configured' :
216276
hasApiKeyAuth() ?
217277
`API Key using header: ${AUTH_APIKEY_HEADER_NAME}` :
218278
'No authentication configured'
219-
}
220-
221-
The tool automatically:
222-
- Normalizes endpoints (adds leading slash, removes trailing slashes)
223-
- Handles authentication header injection
224-
- Accepts any HTTP status code as valid
225-
- Limits response size to ${RESPONSE_SIZE_LIMIT} bytes (see config resource for size limit settings)
226-
- Returns detailed response information including:
227-
* Full URL called
228-
* Status code and text
229-
* Response headers
230-
* Response body
231-
* Request details (method, headers, body)
232-
* Response timing
233-
* Validation messages
234-
235-
Error Handling:
236-
- Network errors are caught and returned with descriptive messages
237-
- Invalid status codes are still returned with full response details
238-
- Authentication errors include the attempted auth method
239-
240-
See the config resource for all configuration options.
279+
} | ${(() => {
280+
const customHeaders = getCustomHeaders();
281+
if (Object.keys(customHeaders).length === 0) {
282+
return 'No custom headers defined (see config resource for headers)';
283+
}
284+
285+
// List of common headers that are safe to show values for
286+
const safeHeaders = new Set([
287+
'accept',
288+
'accept-language',
289+
'content-type',
290+
'user-agent',
291+
'cache-control',
292+
'if-match',
293+
'if-none-match',
294+
'if-modified-since',
295+
'if-unmodified-since'
296+
]);
297+
298+
const headerList = Object.entries(customHeaders).map(([name, value]) => {
299+
const lowerName = name.toLowerCase();
300+
return safeHeaders.has(lowerName) ?
301+
`${name}(${value})` :
302+
name;
303+
}).join(', ');
304+
305+
return `Custom headers defined: ${headerList} (see config resource for headers)`;
306+
})()} | The tool automatically: - Normalizes endpoints (adds leading slash, removes trailing slashes) - Handles authentication header injection - Applies custom headers from HEADER_* environment variables - Accepts any HTTP status code as valid - Limits response size to ${RESPONSE_SIZE_LIMIT} bytes (see config resource for size limit settings) - Returns detailed response information including: * Full URL called * Status code and text * Response headers * Response body * Request details (method, headers, body) * Response timing * Validation messages | Error Handling: - Network errors are caught and returned with descriptive messages - Invalid status codes are still returned with full response details - Authentication errors include the attempted auth method | See the config resource for all configuration options, including header configuration.
241307
`,
242308
inputSchema: {
243309
type: 'object',
@@ -249,19 +315,19 @@ See the config resource for all configuration options.
249315
},
250316
endpoint: {
251317
type: 'string',
252-
description: `Endpoint path (e.g. "/users"). Do not include full URLs - only the path. Example: "/api/users" will resolve to "${process.env.REST_BASE_URL}/api/users"`,
318+
description: `Endpoint path (e.g. "/users"). Do not include full URLs - only the path. Example: "/api/users" will resolve to "${normalizeBaseUrl(process.env.REST_BASE_URL!)}/api/users"`,
253319
},
254320
body: {
255321
type: 'object',
256322
description: 'Optional request body for POST/PUT requests',
257323
},
258324
headers: {
259325
type: 'object',
260-
description: 'Optional request headers',
326+
description: 'Optional request headers for one-time use. IMPORTANT: Do not use for sensitive data like API keys - those should be configured via environment variables. This parameter is intended for dynamic, non-sensitive headers that may be needed for specific requests.',
261327
additionalProperties: {
262-
type: 'string',
263-
},
264-
},
328+
type: 'string'
329+
}
330+
}
265331
},
266332
required: ['method', 'endpoint'],
267333
},
@@ -291,15 +357,25 @@ See the config resource for all configuration options.
291357
const config: AxiosRequestConfig = {
292358
method: request.params.arguments.method as Method,
293359
url: normalizedEndpoint,
294-
headers: request.params.arguments.headers || {},
360+
headers: {},
295361
};
296362

297363
// Add request body for POST/PUT
298364
if (['POST', 'PUT'].includes(request.params.arguments.method) && request.params.arguments.body) {
299365
config.data = request.params.arguments.body;
300366
}
301367

302-
// Handle authentication based on environment variables
368+
// Apply headers in order of priority (lowest to highest)
369+
370+
// 1. Custom global headers (lowest priority)
371+
const customHeaders = getCustomHeaders();
372+
config.headers = {
373+
...customHeaders,
374+
...config.headers,
375+
...(request.params.arguments.headers || {}) // Request-specific headers (middle priority)
376+
};
377+
378+
// 3. Authentication headers (highest priority)
303379
if (hasBasicAuth()) {
304380
const base64Credentials = Buffer.from(`${AUTH_BASIC_USERNAME}:${AUTH_BASIC_PASSWORD}`).toString('base64');
305381
config.headers = {
@@ -335,15 +411,18 @@ See the config resource for all configuration options.
335411
request: {
336412
url: fullUrl,
337413
method: config.method || 'GET',
338-
headers: config.headers as Record<string, string | undefined>,
414+
headers: {
415+
...sanitizeHeaders(config.headers as Record<string, string | undefined>, false),
416+
...sanitizeHeaders(request.params.arguments.headers || {}, true)
417+
},
339418
body: config.data,
340419
authMethod
341420
},
342421
response: {
343422
statusCode: response.status,
344423
statusText: response.statusText,
345424
timing: `${endTime - startTime}ms`,
346-
headers: response.headers as Record<string, any>,
425+
headers: sanitizeHeaders(response.headers as Record<string, any>, false),
347426
body: response.data,
348427
},
349428
validation: {
@@ -395,7 +474,10 @@ See the config resource for all configuration options.
395474
request: {
396475
url: `${process.env.REST_BASE_URL}${normalizedEndpoint}`,
397476
method: config.method,
398-
headers: config.headers,
477+
headers: {
478+
...sanitizeHeaders(config.headers as Record<string, string | undefined>, false),
479+
...sanitizeHeaders(request.params.arguments.headers || {}, true)
480+
},
399481
body: config.data
400482
}
401483
}

src/resources/config.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,20 @@ This document describes all available configuration options for the REST API tes
2121
- Values: Set to `false` to disable SSL verification for self-signed certificates
2222
- Usage: Disable when testing APIs with self-signed certificates in development environments
2323

24+
## Custom Headers Configuration
25+
26+
### Custom Headers (Optional)
27+
- Description: Add custom headers to all requests using environment variables
28+
- Pattern: `HEADER_<HeaderName>=<Value>` (prefix is case-insensitive)
29+
- Examples:
30+
```bash
31+
HEADER_X-API-Version=2.0
32+
header_Custom-Client=my-client
33+
HeAdEr_Accept=application/json
34+
```
35+
- Usage: Headers are added to all requests. The header name after `HEADER_` preserves its exact case
36+
- Priority: Per-request headers > Authentication headers > Custom global headers
37+
2438
## Authentication Configuration
2539

2640
The tool supports three authentication methods. Configure one based on your API's requirements.
@@ -73,6 +87,14 @@ REST_APIKEY_HEADER_NAME=X-API-Key
7387
REST_APIKEY_VALUE=your-api-key
7488
```
7589

90+
### API with Custom Headers
91+
```bash
92+
REST_BASE_URL=https://api.example.com
93+
HEADER_X-API-Version=2.0
94+
HEADER_Custom-Client=my-client
95+
HEADER_Accept=application/json
96+
```
97+
7698
## Changing Configuration
7799

78100
Configuration can be updated by:

0 commit comments

Comments
 (0)