#
tokens: 15208/50000 18/18 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .github
│   ├── conventional-changelog.config.cjs
│   ├── conventional-changelog.config.js
│   └── workflows
│       ├── pr-validation.yml
│       ├── publish.yml
│       └── refresh-badges.yml
├── .gitignore
├── CHANGELOG.md
├── commitlint.config.js
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   └── build.js
├── smithery.yaml
├── src
│   ├── index.ts
│   ├── resources
│   │   ├── config.md
│   │   ├── examples.md
│   │   └── response-format.md
│   └── version.ts.template
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules/
2 | build/
3 | src/version.ts
4 | .DS_Store
5 | *.log
6 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/dkmaker-mcp-rest-api-badge.png)](https://mseep.ai/app/dkmaker-mcp-rest-api)
  2 | 
  3 | # MCP REST API Tester
  4 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
  5 | [![NPM Package](https://img.shields.io/npm/v/dkmaker-mcp-rest-api.svg)](https://www.npmjs.com/package/dkmaker-mcp-rest-api)
  6 | [![smithery badge](https://smithery.ai/badge/dkmaker-mcp-rest-api)](https://smithery.ai/server/dkmaker-mcp-rest-api)
  7 | 
  8 | A TypeScript-based MCP server that enables testing of REST APIs through Cline. This tool allows you to test and interact with any REST API endpoints directly from your development environment.
  9 | 
 10 | <a href="https://glama.ai/mcp/servers/izr2sp4rqo">
 11 |   <img width="380" height="200" src="https://glama.ai/mcp/servers/izr2sp4rqo/badge?refresh=1234" />
 12 | </a>
 13 | 
 14 | ## Installation
 15 | 
 16 | ### Installing via Smithery
 17 | 
 18 | To install REST API Tester for Claude Desktop automatically via [Smithery](https://smithery.ai/server/dkmaker-mcp-rest-api):
 19 | 
 20 | ```bash
 21 | npx -y @smithery/cli install dkmaker-mcp-rest-api --client claude
 22 | ```
 23 | 
 24 | ### Installing Manually
 25 | 1. Install the package globally:
 26 | ```bash
 27 | npm install -g dkmaker-mcp-rest-api
 28 | ```
 29 | 
 30 | 2. Configure Cline Custom Instructions:
 31 | 
 32 | To ensure Cline understands how to effectively use this tool, add the following to your Cline custom instructions (Settings > Custom Instructions):
 33 | 
 34 | ```markdown
 35 | # REST API Testing Instructions
 36 | 
 37 | The `test_request` tool enables testing, debugging, and interacting with REST API endpoints. The tool provides comprehensive request/response information and handles authentication automatically.
 38 | 
 39 | ## When to Use
 40 | 
 41 | - Testing specific API endpoints
 42 | - Debugging API responses
 43 | - Verifying API functionality
 44 | - Checking response times
 45 | - Validating request/response formats
 46 | - Testing local development servers
 47 | - Testing API sequences
 48 | - Verifying error handling
 49 | 
 50 | ## Key Features
 51 | 
 52 | - Supports GET, POST, PUT, DELETE, PATCH methods
 53 | - Handles authentication (Basic, Bearer, API Key)
 54 | - Normalizes endpoints automatically
 55 | - Provides detailed response information
 56 | - Configurable SSL verification and response limits
 57 | 
 58 | ## Resources
 59 | 
 60 | The following resources provide detailed documentation:
 61 | 
 62 | - examples: Usage examples and common patterns
 63 | - response-format: Response structure and fields
 64 | - config: Configuration options and setup guide
 65 | 
 66 | Access these resources to understand usage, response formats, and configuration options.
 67 | 
 68 | ## Important Notes
 69 | 
 70 | - Review API implementation for expected behavior
 71 | - Handle sensitive data appropriately
 72 | - Consider rate limits and API constraints
 73 | - Restart server after configuration changes
 74 | ```
 75 | 
 76 | 3. Add the server to your MCP configuration:
 77 | 
 78 | While these instructions are for Cline, the server should work with any MCP implementation. Configure based on your operating system:
 79 | 
 80 | ### Windows
 81 | ⚠️ **IMPORTANT**: Due to a known issue with Windows path resolution ([issue #40](https://github.com/modelcontextprotocol/servers/issues/40)), you must use the full path instead of %APPDATA%.
 82 | 
 83 | Add to `C:\Users\<YourUsername>\AppData\Roaming\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json`:
 84 | ```json
 85 | {
 86 |   "mcpServers": {
 87 |     "rest-api": {
 88 |       "command": "node",
 89 |       "args": [
 90 |         "C:/Users/<YourUsername>/AppData/Roaming/npm/node_modules/dkmaker-mcp-rest-api/build/index.js"
 91 |       ],
 92 |       "env": {
 93 |         "REST_BASE_URL": "https://api.example.com",
 94 |         // Basic Auth
 95 |         "AUTH_BASIC_USERNAME": "your-username",
 96 |         "AUTH_BASIC_PASSWORD": "your-password",
 97 |         // OR Bearer Token
 98 |         "AUTH_BEARER": "your-token",
 99 |         // OR API Key
100 |         "AUTH_APIKEY_HEADER_NAME": "X-API-Key",
101 |         "AUTH_APIKEY_VALUE": "your-api-key",
102 |         // SSL Verification (enabled by default)
103 |         "REST_ENABLE_SSL_VERIFY": "false", // Set to false to disable SSL verification for self-signed certificates
104 |         // Response Size Limit (optional, defaults to 10000 bytes)
105 |         "REST_RESPONSE_SIZE_LIMIT": "10000", // Maximum response size in bytes
106 |         // Custom Headers (optional)
107 |         "HEADER_X-API-Version": "2.0",
108 |         "HEADER_Custom-Client": "my-client",
109 |         "HEADER_Accept": "application/json"
110 |       }
111 |     }
112 |   }
113 | }
114 | ```
115 | 
116 | ### macOS
117 | Add to `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`:
118 | ```json
119 | {
120 |   "mcpServers": {
121 |     "rest-api": {
122 |       "command": "npx",
123 |       "args": [
124 |         "-y",
125 |         "dkmaker-mcp-rest-api"
126 |       ],
127 |       "env": {
128 |         "REST_BASE_URL": "https://api.example.com",
129 |         // Basic Auth
130 |         "AUTH_BASIC_USERNAME": "your-username",
131 |         "AUTH_BASIC_PASSWORD": "your-password",
132 |         // OR Bearer Token
133 |         "AUTH_BEARER": "your-token",
134 |         // OR API Key
135 |         "AUTH_APIKEY_HEADER_NAME": "X-API-Key",
136 |         "AUTH_APIKEY_VALUE": "your-api-key",
137 |         // SSL Verification (enabled by default)
138 |         "REST_ENABLE_SSL_VERIFY": "false", // Set to false to disable SSL verification for self-signed certificates
139 |         // Custom Headers (optional)
140 |         "HEADER_X-API-Version": "2.0",
141 |         "HEADER_Custom-Client": "my-client",
142 |         "HEADER_Accept": "application/json"
143 |       }
144 |     }
145 |   }
146 | }
147 | ```
148 | 
149 | Note: Replace the environment variables with your actual values. Only configure one authentication method at a time:
150 | 1. Basic Authentication (username/password)
151 | 2. Bearer Token (if Basic Auth is not configured)
152 | 3. API Key (if neither Basic Auth nor Bearer Token is configured)
153 | 
154 | ## Features
155 | 
156 | - Test REST API endpoints with different HTTP methods
157 | - Support for GET, POST, PUT, DELETE, and PATCH requests
158 | - Detailed response information including status, headers, and body
159 | - Custom Headers:
160 |   - Global headers via HEADER_* environment variables
161 |   - Case-insensitive prefix (HEADER_, header_, HeAdEr_)
162 |   - Case preservation for header names
163 |   - Priority-based application (per-request > auth > custom)
164 | - Request body handling for POST/PUT methods
165 | - Response Size Management:
166 |   - Automatic response size limiting (default: 10KB/10000 bytes)
167 |   - Configurable size limit via REST_RESPONSE_SIZE_LIMIT environment variable
168 |   - Clear truncation metadata when responses exceed limit
169 |   - Preserves response structure while only truncating body content
170 | 
171 | - SSL Certificate Verification:
172 |   - Enabled by default for secure operation
173 |   - Can be disabled for self-signed certificates or development environments
174 |   - Control via REST_ENABLE_SSL_VERIFY environment variable
175 | - Multiple authentication methods:
176 |   - Basic Authentication (username/password)
177 |   - Bearer Token Authentication
178 |   - API Key Authentication (custom header)
179 | 
180 | ## Usage Examples
181 | 
182 | Once installed and configured, you can use the REST API Tester through Cline to test your API endpoints:
183 | 
184 | ```typescript
185 | // Test a GET endpoint
186 | use_mcp_tool('rest-api', 'test_request', {
187 |   "method": "GET",
188 |   "endpoint": "/users"
189 | });
190 | 
191 | // Test a POST endpoint with body
192 | use_mcp_tool('rest-api', 'test_request', {
193 |   "method": "POST",
194 |   "endpoint": "/users",
195 |   "body": {
196 |     "name": "John Doe",
197 |     "email": "[email protected]"
198 |   }
199 | });
200 | 
201 | // Test with custom headers
202 | use_mcp_tool('rest-api', 'test_request', {
203 |   "method": "GET",
204 |   "endpoint": "/products",
205 |   "headers": {
206 |     "Accept-Language": "en-US",
207 |     "X-Custom-Header": "custom-value"
208 |   }
209 | });
210 | ```
211 | 
212 | ## Development
213 | 
214 | 1. Clone the repository:
215 | ```bash
216 | git clone https://github.com/zenturacp/mcp-rest-api.git
217 | cd mcp-rest-api
218 | ```
219 | 
220 | 2. Install dependencies:
221 | ```bash
222 | npm install
223 | ```
224 | 
225 | 3. Build the project:
226 | ```bash
227 | npm run build
228 | ```
229 | 
230 | For development with auto-rebuild:
231 | ```bash
232 | npm run watch
233 | ```
234 | 
235 | ## License
236 | 
237 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
238 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/.github/workflows/refresh-badges.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Refresh Badges
 2 | 
 3 | on:
 4 |   schedule:
 5 |     - cron: '0 0 * * *'  # Run at 00:00 UTC every day
 6 |   push:
 7 |     branches:
 8 |       - main
 9 |   workflow_dispatch:  # Allow manual triggering
10 | 
11 | jobs:
12 |   refresh:
13 |     runs-on: ubuntu-latest
14 |     steps:
15 |       - name: Refresh badges
16 |         uses: b3b00/[email protected]
17 |         with:
18 |           repository: 'zenturacp/mcp-rest-api'
19 |           branch: 'main'
20 | 
```

--------------------------------------------------------------------------------
/.github/conventional-changelog.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | module.exports = {
 2 |   types: [
 3 |     { type: 'feat', section: 'Features' },
 4 |     { type: 'fix', section: 'Bug Fixes' },
 5 |     { type: 'chore', section: 'Maintenance' },
 6 |     { type: 'docs', section: 'Documentation' },
 7 |     { type: 'style', section: 'Styling' },
 8 |     { type: 'refactor', section: 'Code Refactoring' },
 9 |     { type: 'perf', section: 'Performance' },
10 |     { type: 'test', section: 'Testing' },
11 |     { type: 'ci', section: 'CI/CD' },
12 |     { type: 'build', section: 'Build System' }
13 |   ]
14 | };
15 | 
```

--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | export default {
 2 |   extends: ['@commitlint/config-conventional'],
 3 |   rules: {
 4 |     'type-enum': [
 5 |       2,
 6 |       'always',
 7 |       [
 8 |         'build',
 9 |         'chore',
10 |         'ci',
11 |         'docs',
12 |         'feat',
13 |         'fix',
14 |         'perf',
15 |         'refactor',
16 |         'revert',
17 |         'style',
18 |         'test'
19 |       ]
20 |     ],
21 |     'type-case': [2, 'always', 'lower-case'],
22 |     'type-empty': [2, 'never'],
23 |     'scope-case': [2, 'always', 'lower-case'],
24 |     'subject-case': [2, 'always', 'lower-case'],
25 |     'subject-empty': [2, 'never'],
26 |     'subject-full-stop': [2, 'never', '.']
27 |   }
28 | }
29 | 
```

--------------------------------------------------------------------------------
/.github/workflows/pr-validation.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Pull Request Validation
 2 | 
 3 | on:
 4 |   pull_request:
 5 |     branches: [ main ]
 6 | 
 7 | jobs:
 8 |   validate:
 9 |     runs-on: ubuntu-latest
10 |     steps:
11 |       - uses: actions/checkout@v4
12 |         with:
13 |           fetch-depth: 0
14 | 
15 |       - name: Setup Node.js
16 |         uses: actions/setup-node@v4
17 |         with:
18 |           node-version: '18'
19 |           cache: 'npm'
20 | 
21 |       - name: Install dependencies
22 |         run: npm ci
23 | 
24 |       - name: Validate Conventional Commits
25 |         run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose
26 | 
27 |       - name: Build
28 |         run: npm run build
29 | 
30 |       - name: Run Tests
31 |         run: |
32 |           if [ -f "package.json" ] && grep -q "\"test\":" "package.json"; then
33 |             npm test
34 |           else
35 |             echo "No test script found in package.json"
36 |           fi
37 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
 2 | # Use the Node.js image with the required version for the project
 3 | FROM node:18-alpine AS builder
 4 | 
 5 | # Set working directory
 6 | WORKDIR /app
 7 | 
 8 | # Copy package files to the working directory
 9 | COPY package.json package-lock.json ./
10 | 
11 | # Install dependencies
12 | RUN npm install
13 | 
14 | # Copy all files to the working directory
15 | COPY . .
16 | 
17 | # Build the TypeScript files
18 | RUN npm run build
19 | 
20 | # Create the final release image
21 | FROM node:18-alpine AS release
22 | 
23 | # Set working directory
24 | WORKDIR /app
25 | 
26 | # Copy built files and necessary package information
27 | COPY --from=builder /app/build /app/build
28 | COPY --from=builder /app/package.json /app/package-lock.json /app/node_modules ./ 
29 | 
30 | # Environment configuration for runtime (configured externally)
31 | ENV REST_BASE_URL=""
32 | ENV AUTH_BASIC_USERNAME=""
33 | ENV AUTH_BASIC_PASSWORD=""
34 | ENV AUTH_BEARER=""
35 | ENV AUTH_APIKEY_HEADER_NAME=""
36 | ENV AUTH_APIKEY_VALUE=""
37 | ENV REST_ENABLE_SSL_VERIFY="true"
38 | ENV REST_RESPONSE_SIZE_LIMIT="10000"
39 | 
40 | # Command to run the server
41 | ENTRYPOINT ["node", "build/index.js"]
```

--------------------------------------------------------------------------------
/.github/conventional-changelog.config.cjs:
--------------------------------------------------------------------------------

```
 1 | module.exports = {
 2 |   types: [
 3 |     { type: 'feat', section: 'Features', hidden: false },
 4 |     { type: 'fix', section: 'Bug Fixes', hidden: false },
 5 |     { type: 'chore', section: 'Maintenance', hidden: false },
 6 |     { type: 'docs', section: 'Documentation', hidden: false },
 7 |     { type: 'style', section: 'Styling', hidden: false },
 8 |     { type: 'refactor', section: 'Code Refactoring', hidden: false },
 9 |     { type: 'perf', section: 'Performance', hidden: false },
10 |     { type: 'test', section: 'Testing', hidden: false },
11 |     { type: 'ci', section: 'CI/CD', hidden: false },
12 |     { type: 'build', section: 'Build System', hidden: false }
13 |   ],
14 |   releaseRules: [
15 |     { type: 'feat', release: 'minor' },
16 |     { type: 'fix', release: 'patch' },
17 |     { type: 'perf', release: 'patch' },
18 |     { type: 'chore', release: 'patch' },
19 |     { type: 'docs', release: 'patch' },
20 |     { type: 'style', release: 'patch' },
21 |     { type: 'refactor', release: 'patch' },
22 |     { type: 'test', release: 'patch' },
23 |     { type: 'ci', release: 'patch' },
24 |     { type: 'build', release: 'patch' }
25 |   ]
26 | };
27 | 
```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
 1 | ## [0.4.0](https://github.com/dkmaker/mcp-rest-api/compare/v0.3.0...v0.4.0) (2025-01-08)
 2 | 
 3 | 
 4 | ### Features
 5 | 
 6 | * add custom header support ([9a48e0d](https://github.com/dkmaker/mcp-rest-api/commit/9a48e0d794a743f7a62c7cb73d6f5b1be9e44107))
 7 | 
 8 | ## [0.3.0](https://github.com/dkmaker/mcp-rest-api/compare/v0.2.0...v0.3.0) (2024-12-28)
 9 | 
10 | 
11 | ### Features
12 | 
13 | * add config documentation and improve URL resolution examples ([8c6100f](https://github.com/dkmaker/mcp-rest-api/commit/8c6100f47777605a0571edbd161ffd20fc48b640))
14 | * add MCP resources for documentation ([a20cf35](https://github.com/dkmaker/mcp-rest-api/commit/a20cf352e9731841a8d7e833007a96bdd1a0c390))
15 | 
16 | 
17 | ### Bug Fixes
18 | 
19 | * correct response truncation to return first N bytes ([ce34649](https://github.com/dkmaker/mcp-rest-api/commit/ce34649c4d8e6bc6d740e8f3fbc6e3df517e0eec))
20 | 
21 | ## [0.2.0](https://github.com/dkmaker/mcp-rest-api/compare/0fdbe844dd4ce8b79f38a33df323a29e28253724...v0.2.0) (2024-12-21)
22 | 
23 | 
24 | ### Features
25 | 
26 | * **ssl:** add SSL verification control with secure defaults ([0fdbe84](https://github.com/dkmaker/mcp-rest-api/commit/0fdbe844dd4ce8b79f38a33df323a29e28253724))
27 | 
28 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "dkmaker-mcp-rest-api",
 3 |   "version": "0.4.0",
 4 |   "description": "A generic REST API tester for testing HTTP endpoints",
 5 |   "license": "MIT",
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "dkmaker-mcp-rest-api": "./build/index.js"
 9 |   },
10 |   "files": [
11 |     "build",
12 |     "README.md",
13 |     "LICENSE"
14 |   ],
15 |   "scripts": {
16 |     "prebuild": "node scripts/build.js",
17 |     "build": "tsc",
18 |     "prepare": "npm run build",
19 |     "watch": "tsc --watch",
20 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js"
21 |   },
22 |   "dependencies": {
23 |     "@modelcontextprotocol/sdk": "^1.0.4",
24 |     "axios": "^1.7.9"
25 |   },
26 |   "devDependencies": {
27 |     "@commitlint/cli": "^19.6.1",
28 |     "@commitlint/config-conventional": "^19.6.0",
29 |     "@types/node": "^20.11.24",
30 |     "typescript": "^5.3.3"
31 |   },
32 |   "keywords": [
33 |     "mcp",
34 |     "rest",
35 |     "api",
36 |     "http",
37 |     "testing",
38 |     "cline",
39 |     "development",
40 |     "typescript"
41 |   ],
42 |   "author": "zenturacp",
43 |   "repository": {
44 |     "type": "git",
45 |     "url": "git+https://github.com/dkmaker/mcp-rest-api.git"
46 |   },
47 |   "bugs": {
48 |     "url": "https://github.com/dkmaker/mcp-rest-api/issues"
49 |   },
50 |   "homepage": "https://github.com/dkmaker/mcp-rest-api#readme",
51 |   "engines": {
52 |     "node": ">=18.0.0"
53 |   }
54 | }
55 | 
```

--------------------------------------------------------------------------------
/src/resources/response-format.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Response Format Documentation
 2 | 
 3 | The REST API testing tool returns a comprehensive JSON response containing request details, response information, and validation results.
 4 | 
 5 | ## Response Structure
 6 | 
 7 | ```json
 8 | {
 9 |   "request": {
10 |     "url": "http://api.example.com/users",
11 |     "method": "GET",
12 |     "headers": {},
13 |     "body": null,
14 |     "authMethod": "none"
15 |   },
16 |   "response": {
17 |     "statusCode": 200,
18 |     "statusText": "OK",
19 |     "timing": "123ms",
20 |     "headers": {},
21 |     "body": {}
22 |   },
23 |   "validation": {
24 |     "isError": false,
25 |     "messages": ["Request completed successfully"]
26 |   }
27 | }
28 | ```
29 | 
30 | ## Response Fields
31 | 
32 | ### Request Details
33 | - `url`: Full URL including base URL and endpoint
34 | - `method`: HTTP method used
35 | - `headers`: Request headers sent
36 | - `body`: Request body (if applicable)
37 | - `authMethod`: Authentication method used (none, basic, bearer, or apikey)
38 | 
39 | ### Response Details
40 | - `statusCode`: HTTP status code
41 | - `statusText`: Status message
42 | - `timing`: Request duration in milliseconds
43 | - `headers`: Response headers received
44 | - `body`: Response body content
45 | 
46 | ### Validation
47 | - `isError`: true if status code >= 400
48 | - `messages`: Array of validation or error messages
49 | 
50 | ## Error Response Example
51 | 
52 | ```json
53 | {
54 |   "error": {
55 |     "message": "Connection refused",
56 |     "code": "ECONNREFUSED",
57 |     "request": {
58 |       "url": "http://api.example.com/users",
59 |       "method": "GET",
60 |       "headers": {},
61 |       "body": null
62 |     }
63 |   }
64 | }
65 | 
```

--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------

```javascript
 1 | #!/usr/bin/env node
 2 | import fs from 'fs/promises';
 3 | import path from 'path';
 4 | import { fileURLToPath } from 'url';
 5 | 
 6 | const __filename = fileURLToPath(import.meta.url);
 7 | const __dirname = path.dirname(__filename);
 8 | 
 9 | async function main() {
10 |   const pkg = JSON.parse(
11 |     await fs.readFile(
12 |       path.join(__dirname, '..', 'package.json'),
13 |       'utf8'
14 |     )
15 |   );
16 | 
17 |   const versionPath = path.join(__dirname, '..', 'src', 'version.ts');
18 | 
19 |   // Always generate version.ts with actual values during build
20 |   const content = `// Auto-generated by build script
21 | export const VERSION = '${pkg.version}';
22 | export const PACKAGE_NAME = '${pkg.name}';
23 | export const SERVER_NAME = '${pkg.name.split('-').slice(-2).join('-')}';
24 | `;
25 | 
26 |   await fs.writeFile(versionPath, content);
27 |   console.log('Generated version.ts with package values');
28 | 
29 |   // Copy resources to build directory
30 |   const resourcesSrcDir = path.join(__dirname, '..', 'src', 'resources');
31 |   const resourcesBuildDir = path.join(__dirname, '..', 'build', 'resources');
32 |   
33 |   try {
34 |     await fs.mkdir(resourcesBuildDir, { recursive: true });
35 |     const files = await fs.readdir(resourcesSrcDir);
36 |     
37 |     for (const file of files) {
38 |       await fs.copyFile(
39 |         path.join(resourcesSrcDir, file),
40 |         path.join(resourcesBuildDir, file)
41 |       );
42 |     }
43 |     console.log('Copied resources to build directory');
44 |   } catch (error) {
45 |     console.error('Error copying resources:', error);
46 |     throw error;
47 |   }
48 | }
49 | 
50 | main().catch(console.error);
51 | 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required:
 9 |       - restBaseUrl
10 |     properties:
11 |       restBaseUrl:
12 |         type: string
13 |         description: The base URL for the REST API.
14 |       authBasicUsername:
15 |         type: string
16 |         description: The username for Basic Authentication.
17 |       authBasicPassword:
18 |         type: string
19 |         description: The password for Basic Authentication.
20 |       authBearer:
21 |         type: string
22 |         description: The bearer token for authentication.
23 |       authApiKeyHeaderName:
24 |         type: string
25 |         description: The header name for API Key Authentication.
26 |       authApiKeyValue:
27 |         type: string
28 |         description: The API key value for API Key Authentication.
29 |       restEnableSslVerify:
30 |         type: boolean
31 |         default: true
32 |         description: Enable or disable SSL verification.
33 |       restResponseSizeLimit:
34 |         type: number
35 |         default: 10000
36 |         description: The maximum response size limit in bytes.
37 |   commandFunction:
38 |     # A function that produces the CLI command to start the MCP on stdio.
39 |     |-
40 |     config => ({ command: 'node', args: ['build/index.js'], env: { REST_BASE_URL: config.restBaseUrl, AUTH_BASIC_USERNAME: config.authBasicUsername, AUTH_BASIC_PASSWORD: config.authBasicPassword, AUTH_BEARER: config.authBearer, AUTH_APIKEY_HEADER_NAME: config.authApiKeyHeaderName, AUTH_APIKEY_VALUE: config.authApiKeyValue, REST_ENABLE_SSL_VERIFY: config.restEnableSslVerify.toString(), REST_RESPONSE_SIZE_LIMIT: config.restResponseSizeLimit.toString() } })
```

--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Publish Package
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [ main ]
 6 |   workflow_dispatch:
 7 | 
 8 | permissions:
 9 |   contents: write
10 |   pull-requests: write
11 | 
12 | jobs:
13 |   publish:
14 |     runs-on: ubuntu-latest
15 |     steps:
16 |       - uses: actions/checkout@v4
17 |         with:
18 |           fetch-depth: 0
19 |       
20 |       - name: Setup Node.js
21 |         uses: actions/setup-node@v4
22 |         with:
23 |           node-version: '18.x'
24 |           registry-url: 'https://registry.npmjs.org'
25 | 
26 |       - name: Install dependencies
27 |         run: npm ci
28 | 
29 |       - name: Conventional Changelog Action
30 |         id: changelog
31 |         uses: TriPSs/conventional-changelog-action@v6
32 |         with:
33 |           github-token: ${{ secrets.GITHUB_TOKEN }}
34 |           git-message: 'chore(release): {version}'
35 |           config-file-path: '.github/conventional-changelog.config.cjs'
36 |           tag-prefix: 'v'
37 |           output-file: 'CHANGELOG.md'
38 |           skip-version-file: false
39 |           skip-commit: false
40 |           skip-on-empty: false
41 |           git-user-name: ${{ secrets.CHANGELOG_GIT_NAME }}
42 |           git-user-email: ${{ secrets.CHANGELOG_GIT_EMAIL }}
43 | 
44 |       - name: Build
45 |         run: npm run build
46 | 
47 |       - name: Create Release
48 |         if: steps.changelog.outputs.skipped == 'false'
49 |         env:
50 |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51 |         run: |
52 |           gh release create v${{ steps.changelog.outputs.version }} \
53 |             --title "Release v${{ steps.changelog.outputs.version }}" \
54 |             --notes "${{ steps.changelog.outputs.clean_changelog }}"
55 | 
56 |       - name: Publish to NPM
57 |         if: steps.changelog.outputs.skipped == 'false'
58 |         run: npm publish
59 |         env:
60 |           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
61 | 
```

--------------------------------------------------------------------------------
/src/resources/config.md:
--------------------------------------------------------------------------------

```markdown
  1 | # REST API Tester Configuration
  2 | 
  3 | This document describes all available configuration options for the REST API testing tool.
  4 | 
  5 | ## Core Configuration
  6 | 
  7 | ### REST_BASE_URL (Required)
  8 | - Description: The base URL that all endpoint paths will be resolved against
  9 | - Example: `http://localhost:3000` or `https://api.example.com`
 10 | - Usage: All endpoint paths will be appended to this URL. For example, if REST_BASE_URL is `http://localhost:3000` and you use the endpoint `/api/users`, the full URL will be `http://localhost:3000/api/users`
 11 | 
 12 | ### REST_RESPONSE_SIZE_LIMIT (Optional)
 13 | - Description: Maximum size in bytes for response data
 14 | - Default: 10000 (10KB)
 15 | - Example: `50000` for 50KB limit
 16 | - Usage: Helps prevent memory issues with large responses. If a response exceeds this size, it will be truncated and a warning message will be included
 17 | 
 18 | ### REST_ENABLE_SSL_VERIFY (Optional)
 19 | - Description: Controls SSL certificate verification
 20 | - Default: `true`
 21 | - Values: Set to `false` to disable SSL verification for self-signed certificates
 22 | - Usage: Disable when testing APIs with self-signed certificates in development environments
 23 | 
 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 | 
 38 | ## Authentication Configuration
 39 | 
 40 | The tool supports three authentication methods. Configure one based on your API's requirements.
 41 | 
 42 | ### Basic Authentication
 43 | - REST_BASIC_USERNAME: Username for Basic Auth
 44 | - REST_BASIC_PASSWORD: Password for Basic Auth
 45 | - Usage: When both are set, requests will include Basic Auth header
 46 | 
 47 | ### Bearer Token
 48 | - REST_BEARER: Bearer token value
 49 | - Usage: When set, requests will include `Authorization: Bearer <token>` header
 50 | 
 51 | ### API Key
 52 | - REST_APIKEY_HEADER_NAME: Name of the header for API key
 53 | - REST_APIKEY_VALUE: Value of the API key
 54 | - Example:
 55 |   ```
 56 |   REST_APIKEY_HEADER_NAME=X-API-Key
 57 |   REST_APIKEY_VALUE=your-api-key-here
 58 |   ```
 59 | - Usage: When both are set, requests will include the specified header with the API key
 60 | 
 61 | ## Configuration Examples
 62 | 
 63 | ### Local Development
 64 | ```bash
 65 | REST_BASE_URL=http://localhost:3000
 66 | REST_ENABLE_SSL_VERIFY=false
 67 | REST_RESPONSE_SIZE_LIMIT=50000
 68 | ```
 69 | 
 70 | ### Production API with Bearer Token
 71 | ```bash
 72 | REST_BASE_URL=https://api.example.com
 73 | REST_BEARER=your-bearer-token
 74 | ```
 75 | 
 76 | ### API with Basic Auth
 77 | ```bash
 78 | REST_BASE_URL=https://api.example.com
 79 | REST_BASIC_USERNAME=admin
 80 | REST_BASIC_PASSWORD=secret
 81 | ```
 82 | 
 83 | ### API with API Key
 84 | ```bash
 85 | REST_BASE_URL=https://api.example.com
 86 | REST_APIKEY_HEADER_NAME=X-API-Key
 87 | REST_APIKEY_VALUE=your-api-key
 88 | ```
 89 | 
 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 | 
 98 | ## Changing Configuration
 99 | 
100 | Configuration can be updated by:
101 | 1. Setting environment variables before starting the server
102 | 2. Modifying the MCP server configuration file
103 | 3. Using environment variable commands in your terminal
104 | 
105 | Remember to restart the server after changing configuration for the changes to take effect.
106 | 
```

--------------------------------------------------------------------------------
/src/resources/examples.md:
--------------------------------------------------------------------------------

```markdown
  1 | # REST API Testing Examples
  2 | 
  3 | ⚠️ IMPORTANT:
  4 | - Only provide the endpoint path in the `endpoint` argument—do not include full URLs. Your path will be automatically resolved to the full URL using the configured base URL or the optional `host` argument.
  5 | - To override the base URL for a single request, use the optional `host` argument. This must be a valid URL starting with `http://` or `https://`, and may include a path (trailing slashes will be removed).
  6 | 
  7 | For example, if the base URL is `http://localhost:3000`:
  8 | ✅ Correct: `"/api/users"` → Resolves to: `http://localhost:3000/api/users`
  9 | ❌ Incorrect: `"http://localhost:3000/api/users"` or `"www.example.com/api/users"`
 10 | 
 11 | If you use a `host` argument:
 12 | ✅ Correct: `"host": "https://api.example.com/v1", "endpoint": "/users"` → Resolves to: `https://api.example.com/v1/users`
 13 | 
 14 | ## Basic GET Request
 15 | ```typescript
 16 | use_mcp_tool('rest-api', 'test_request', {
 17 |   "method": "GET",
 18 |   "endpoint": "/users"  // Will be appended to REST_BASE_URL or 'host' if provided
 19 | });
 20 | ```
 21 | 
 22 | ## GET with Query Parameters
 23 | ```typescript
 24 | use_mcp_tool('rest-api', 'test_request', {
 25 |   "method": "GET",
 26 |   "endpoint": "/users?role=admin&status=active" // Always a path, not a full URL
 27 | });
 28 | ```
 29 | 
 30 | ## POST Request with Body
 31 | ```typescript
 32 | use_mcp_tool('rest-api', 'test_request', {
 33 |   "method": "POST",
 34 |   "endpoint": "/users",
 35 |   "body": {
 36 |     "name": "John Doe",
 37 |     "email": "[email protected]"
 38 |   }
 39 | });
 40 | ```
 41 | 
 42 | ## Request with Custom Headers
 43 | ```typescript
 44 | use_mcp_tool('rest-api', 'test_request', {
 45 |   "method": "GET",
 46 |   "endpoint": "/secure-resource",
 47 |   "headers": {
 48 |     "Custom-Header": "value",
 49 |     "Another-Header": "another-value"
 50 |   }
 51 | });
 52 | ```
 53 | 
 54 | ## PUT Request Example
 55 | ```typescript
 56 | use_mcp_tool('rest-api', 'test_request', {
 57 |   "method": "PUT",
 58 |   "endpoint": "/users/123",
 59 |   "body": {
 60 |     "name": "Updated Name",
 61 |     "status": "inactive"
 62 |   }
 63 | });
 64 | ```
 65 | 
 66 | ## DELETE Request Example
 67 | ```typescript
 68 | use_mcp_tool('rest-api', 'test_request', {
 69 |   "method": "DELETE",
 70 |   "endpoint": "/users/123"
 71 | });
 72 | ```
 73 | 
 74 | ## PATCH Request Example
 75 | ```typescript
 76 | use_mcp_tool('rest-api', 'test_request', {
 77 |   "method": "PATCH",
 78 |   "endpoint": "/users/123",
 79 |   "body": {
 80 |     "status": "active"
 81 |   }
 82 | });
 83 | ```
 84 | 
 85 | ## Using the Optional `host` Argument
 86 | You can override the default base URL for a single request by providing a `host` argument. This must be a valid URL starting with `http://` or `https://`, and may include a path (trailing slashes will be removed).
 87 | 
 88 | ```typescript
 89 | use_mcp_tool('rest-api', 'test_request', {
 90 |   "method": "GET",
 91 |   "endpoint": "/users",
 92 |   "host": "https://api.example.com/v1" // The request will go to https://api.example.com/v1/users
 93 | });
 94 | ```
 95 | 
 96 | - The `host` argument must include the protocol (http or https).
 97 | - If a path is included, any trailing slash will be removed.
 98 | - If `host` is invalid, you will receive a clear error message.
 99 | - The `endpoint` argument must always be a path, never a full URL.
100 | 
101 | ## Changing Base URL
102 | If you need to test against a different base URL for all requests, update the base URL configuration rather than including the full URL in the endpoint parameter. For a single request, use the `host` argument as shown above.
103 | 
104 | Example:
105 | ```bash
106 | # Instead of this:
107 | ❌ "endpoint": "https://api.example.com/users"  # Wrong - don't include the full URL
108 | 
109 | # Do this:
110 | # 1. Update the base URL configuration to: https://api.example.com
111 | # 2. Then use just the path:
112 | ✅ "endpoint": "/users"  # This will resolve to: https://api.example.com/users
113 | # Or, for a single request:
114 | ✅ "host": "https://api.example.com", "endpoint": "/users"  # This will resolve to: https://api.example.com/users
115 | ```
116 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  4 | import {
  5 |   CallToolRequestSchema,
  6 |   ErrorCode,
  7 |   ListToolsRequestSchema,
  8 |   ListResourcesRequestSchema,
  9 |   ReadResourceRequestSchema,
 10 |   McpError,
 11 | } from '@modelcontextprotocol/sdk/types.js';
 12 | import axios, { AxiosInstance, AxiosRequestConfig, Method } from 'axios';
 13 | import { VERSION, SERVER_NAME } from './version.js';
 14 | 
 15 | if (!process.env.REST_BASE_URL) {
 16 |   throw new Error('REST_BASE_URL environment variable is required');
 17 | }
 18 | 
 19 | // Default response size limit: 10KB (10000 bytes)
 20 | const RESPONSE_SIZE_LIMIT = process.env.REST_RESPONSE_SIZE_LIMIT 
 21 |   ? parseInt(process.env.REST_RESPONSE_SIZE_LIMIT, 10)
 22 |   : 10000;
 23 | 
 24 | if (isNaN(RESPONSE_SIZE_LIMIT) || RESPONSE_SIZE_LIMIT <= 0) {
 25 |   throw new Error('REST_RESPONSE_SIZE_LIMIT must be a positive number');
 26 | }
 27 | const AUTH_BASIC_USERNAME = process.env.AUTH_BASIC_USERNAME;
 28 | const AUTH_BASIC_PASSWORD = process.env.AUTH_BASIC_PASSWORD;
 29 | const AUTH_BEARER = process.env.AUTH_BEARER;
 30 | const AUTH_APIKEY_HEADER_NAME = process.env.AUTH_APIKEY_HEADER_NAME;
 31 | const AUTH_APIKEY_VALUE = process.env.AUTH_APIKEY_VALUE;
 32 | const REST_ENABLE_SSL_VERIFY = process.env.REST_ENABLE_SSL_VERIFY !== 'false';
 33 | 
 34 | interface EndpointArgs {
 35 |   method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
 36 |   endpoint: string;
 37 |   body?: any;
 38 |   headers?: Record<string, string>;
 39 |   host?: string;
 40 | }
 41 | 
 42 | interface ValidationResult {
 43 |   isError: boolean;
 44 |   messages: string[];
 45 |   truncated?: {
 46 |     originalSize: number;
 47 |     returnedSize: number;
 48 |     truncationPoint: number;
 49 |     sizeLimit: number;
 50 |   };
 51 | }
 52 | 
 53 | // Function to sanitize headers by removing sensitive values and non-approved headers
 54 | const sanitizeHeaders = (
 55 |   headers: Record<string, any>,
 56 |   isFromOptionalParams: boolean = false
 57 | ): Record<string, any> => {
 58 |   const sanitized: Record<string, any> = {};
 59 |   
 60 |   for (const [key, value] of Object.entries(headers)) {
 61 |     const lowerKey = key.toLowerCase();
 62 |     
 63 |     // Always include headers from optional parameters
 64 |     if (isFromOptionalParams) {
 65 |       sanitized[key] = value;
 66 |       continue;
 67 |     }
 68 |     
 69 |     // Handle authentication headers
 70 |     if (
 71 |       lowerKey === 'authorization' ||
 72 |       (AUTH_APIKEY_HEADER_NAME && lowerKey === AUTH_APIKEY_HEADER_NAME.toLowerCase())
 73 |     ) {
 74 |       sanitized[key] = '[REDACTED]';
 75 |       continue;
 76 |     }
 77 |     
 78 |     // For headers from config/env
 79 |     const customHeaders = getCustomHeaders();
 80 |     if (key in customHeaders) {
 81 |       // Show value only for headers that are in the approved list
 82 |       const safeHeaders = new Set([
 83 |         'accept',
 84 |         'accept-language',
 85 |         'content-type',
 86 |         'user-agent',
 87 |         'cache-control',
 88 |         'if-match',
 89 |         'if-none-match',
 90 |         'if-modified-since',
 91 |         'if-unmodified-since'
 92 |       ]);
 93 |       const lowerKey = key.toLowerCase();
 94 |       sanitized[key] = safeHeaders.has(lowerKey) ? value : '[REDACTED]';
 95 |     }
 96 |   }
 97 |   
 98 |   return sanitized;
 99 | };
100 | 
101 | interface ResponseObject {
102 |   request: {
103 |     url: string;
104 |     method: string;
105 |     headers: Record<string, string | undefined>;
106 |     body: any;
107 |     authMethod: string;
108 |   };
109 |   response: {
110 |     statusCode: number;
111 |     statusText: string;
112 |     timing: string;
113 |     headers: Record<string, any>;
114 |     body: any;
115 |   };
116 |   validation: ValidationResult;
117 | }
118 | 
119 | const normalizeBaseUrl = (url: string): string => url.replace(/\/+$/, '');
120 | 
121 | const isValidEndpointArgs = (args: any): args is EndpointArgs => {
122 |   if (typeof args !== 'object' || args === null) return false;
123 |   if (!['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(args.method)) return false;
124 |   if (typeof args.endpoint !== 'string') return false;
125 |   if (args.headers !== undefined && (typeof args.headers !== 'object' || args.headers === null)) return false;
126 |   
127 |   // Check if endpoint contains a full URL
128 |   const urlPattern = /^(https?:\/\/|www\.)/i;
129 |   if (urlPattern.test(args.endpoint)) {
130 |     throw new McpError(
131 |       ErrorCode.InvalidParams,
132 |       `Invalid endpoint format. Do not include full URLs. Instead of "${args.endpoint}", use just the path (e.g. "/api/users"). ` +
133 |       `Your path will be resolved to: ${process.env.REST_BASE_URL}${args.endpoint.replace(/^\/+|\/+$/g, '')}. ` +
134 |       `To test a different base URL, update the REST_BASE_URL environment variable.`
135 |     );
136 |   }
137 |   // Validate .host if present
138 |   if (args.host !== undefined) {
139 |     try {
140 |       const url = new URL(args.host);
141 |       if (!/^https?:$/.test(url.protocol)) {
142 |         throw new Error();
143 |       }
144 |       // Remove trailing slash if present
145 |       if (url.pathname.endsWith('/') && url.pathname !== '/') {
146 |         url.pathname = url.pathname.replace(/\/+$/, '');
147 |         args.host = url.origin + url.pathname;
148 |       } else {
149 |         args.host = url.origin + url.pathname;
150 |       }
151 |     } catch (e) {
152 |       throw new McpError(ErrorCode.InvalidParams, `Invalid host format. The 'host' argument must be a valid URL starting with http:// or https://, e.g. "https://example.com" or "http://localhost:3001/api/v1". Received: "${args.host}"`);
153 |     }
154 |   }
155 |   
156 |   return true;
157 | };
158 | 
159 | const hasBasicAuth = () => AUTH_BASIC_USERNAME && AUTH_BASIC_PASSWORD;
160 | const hasBearerAuth = () => !!AUTH_BEARER;
161 | const hasApiKeyAuth = () => AUTH_APIKEY_HEADER_NAME && AUTH_APIKEY_VALUE;
162 | 
163 | // Collect custom headers from environment variables
164 | const getCustomHeaders = (): Record<string, string> => {
165 |   const headers: Record<string, string> = {};
166 |   const headerPrefix = /^header_/i;  // Case-insensitive match for 'header_'
167 |   
168 |   for (const [key, value] of Object.entries(process.env)) {
169 |     if (headerPrefix.test(key) && value !== undefined) {
170 |       // Extract header name after the prefix, preserving case
171 |       const headerName = key.replace(headerPrefix, '');
172 |       headers[headerName] = value;
173 |     }
174 |   }
175 |   
176 |   return headers;
177 | };
178 | 
179 | class RestTester {
180 |   private server!: Server;
181 |   private axiosInstance!: AxiosInstance;
182 | 
183 |   constructor() {
184 |     this.setupServer();
185 |   }
186 | 
187 |   private async setupServer() {
188 |     this.server = new Server(
189 |       {
190 |         name: SERVER_NAME,
191 |         version: VERSION,
192 |       },
193 |       {
194 |         capabilities: {
195 |           tools: {},
196 |           resources: {},
197 |         },
198 |       }
199 |     );
200 | 
201 |     const https = await import('https');
202 |     this.axiosInstance = axios.create({
203 |       baseURL: normalizeBaseUrl(process.env.REST_BASE_URL!),
204 |       validateStatus: () => true, // Allow any status code
205 |       httpsAgent: REST_ENABLE_SSL_VERIFY ? undefined : new https.Agent({ // Disable SSL verification only when explicitly set to false
206 |         rejectUnauthorized: false
207 |       })
208 |     });
209 | 
210 |     this.setupToolHandlers();
211 |     this.setupResourceHandlers();
212 |     
213 |     this.server.onerror = (error) => console.error('[MCP Error]', error);
214 |     process.on('SIGINT', async () => {
215 |       await this.server.close();
216 |       process.exit(0);
217 |     });
218 |   }
219 | 
220 |   private setupResourceHandlers() {
221 |     this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
222 |       resources: [
223 |         {
224 |           uri: `${SERVER_NAME}://examples`,
225 |           name: 'REST API Usage Examples',
226 |           description: 'Detailed examples of using the REST API testing tool',
227 |           mimeType: 'text/markdown'
228 |         },
229 |         {
230 |           uri: `${SERVER_NAME}://response-format`,
231 |           name: 'Response Format Documentation',
232 |           description: 'Documentation of the response format and structure',
233 |           mimeType: 'text/markdown'
234 |         },
235 |         {
236 |           uri: `${SERVER_NAME}://config`,
237 |           name: 'Configuration Documentation',
238 |           description: 'Documentation of all configuration options and how to use them',
239 |           mimeType: 'text/markdown'
240 |         }
241 |       ]
242 |     }));
243 | 
244 |     this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
245 |       const uriPattern = new RegExp(`^${SERVER_NAME}://(.+)$`);
246 |       const match = request.params.uri.match(uriPattern);
247 |       
248 |       if (!match) {
249 |         throw new McpError(
250 |           ErrorCode.InvalidRequest,
251 |           `Invalid resource URI format: ${request.params.uri}`
252 |         );
253 |       }
254 | 
255 |       const resource = match[1];
256 |       const fs = await import('fs');
257 |       const path = await import('path');
258 | 
259 |       try {
260 |         const url = await import('url');
261 |         const __filename = url.fileURLToPath(import.meta.url);
262 |         const __dirname = path.dirname(__filename);
263 |         
264 |         // In the built app, resources are in build/resources
265 |         // In development, they're in src/resources
266 |         const resourcePath = path.join(__dirname, 'resources', `${resource}.md`);
267 |         const content = await fs.promises.readFile(resourcePath, 'utf8');
268 | 
269 |         return {
270 |           contents: [{
271 |             uri: request.params.uri,
272 |             mimeType: 'text/markdown',
273 |             text: content
274 |           }]
275 |         };
276 |       } catch (error) {
277 |         throw new McpError(
278 |           ErrorCode.InvalidRequest,
279 |           `Resource not found: ${resource}`
280 |         );
281 |       }
282 |     });
283 |   }
284 | 
285 |   private setupToolHandlers() {
286 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
287 |       tools: [
288 |         {
289 |           name: 'test_request',
290 |           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: ${
291 |   hasBasicAuth() ? 
292 |     `Basic Auth with username: ${AUTH_BASIC_USERNAME}` :
293 |   hasBearerAuth() ? 
294 |     'Bearer token authentication configured' :
295 |   hasApiKeyAuth() ? 
296 |     `API Key using header: ${AUTH_APIKEY_HEADER_NAME}` :
297 |     'No authentication configured'
298 | } | ${(() => {
299 |   const customHeaders = getCustomHeaders();
300 |   if (Object.keys(customHeaders).length === 0) {
301 |     return 'No custom headers defined (see config resource for headers)';
302 |   }
303 |   
304 |   // List of common headers that are safe to show values for
305 |   const safeHeaders = new Set([
306 |     'accept',
307 |     'accept-language',
308 |     'content-type',
309 |     'user-agent',
310 |     'cache-control',
311 |     'if-match',
312 |     'if-none-match',
313 |     'if-modified-since',
314 |     'if-unmodified-since'
315 |   ]);
316 |   
317 |   const headerList = Object.entries(customHeaders).map(([name, value]) => {
318 |     const lowerName = name.toLowerCase();
319 |     return safeHeaders.has(lowerName) ? 
320 |       `${name}(${value})` : 
321 |       name;
322 |   }).join(', ');
323 |   
324 |   return `Custom headers defined: ${headerList} (see config resource for headers)`;
325 | })()} | 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.
326 | `,
327 |           inputSchema: {
328 |             type: 'object',
329 |             properties: {
330 |               method: {
331 |                 type: 'string',
332 |                 enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
333 |                 description: 'HTTP method to use',
334 |               },
335 |               endpoint: {
336 |                 type: 'string',
337 |                 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"`,
338 |               },
339 |               body: {
340 |                 type: 'object',
341 |                 description: 'Optional request body for POST/PUT requests',
342 |               },
343 |               headers: {
344 |                 type: 'object',
345 |                 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.',
346 |                 additionalProperties: {
347 |                   type: 'string'
348 |                 }
349 |               }
350 |             },
351 |             required: ['method', 'endpoint'],
352 |           },
353 |         },
354 |       ],
355 |     }));
356 | 
357 |     this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
358 |       if (request.params.name !== 'test_request') {
359 |         throw new McpError(
360 |           ErrorCode.MethodNotFound,
361 |           `Unknown tool: ${request.params.name}`
362 |         );
363 |       }
364 | 
365 |       if (!isValidEndpointArgs(request.params.arguments)) {
366 |         throw new McpError(
367 |           ErrorCode.InvalidParams,
368 |           'Invalid test endpoint arguments'
369 |         );
370 |       }
371 | 
372 |       // Ensure endpoint starts with / and remove any trailing slashes
373 |       const normalizedEndpoint = `/${request.params.arguments.endpoint.replace(/^\/+|\/+$/g, '')}`;
374 |       
375 |       const fullUrl = `${request.params.arguments.host || process.env.REST_BASE_URL}${normalizedEndpoint}`;
376 |       // Initialize request config
377 |       const config: AxiosRequestConfig = {
378 |           method: request.params.arguments.method as Method,
379 |           url: fullUrl,
380 |           headers: {},
381 |         };
382 | 
383 |       // Add request body for POST/PUT/PATCH
384 |       if (['POST', 'PUT', 'PATCH'].includes(request.params.arguments.method) && request.params.arguments.body) {
385 |         config.data = request.params.arguments.body;
386 |       }
387 | 
388 |       // Apply headers in order of priority (lowest to highest)
389 |       
390 |       // 1. Custom global headers (lowest priority)
391 |       const customHeaders = getCustomHeaders();
392 |       config.headers = {
393 |         ...customHeaders,
394 |         ...config.headers,
395 |         ...(request.params.arguments.headers || {}) // Request-specific headers (middle priority)
396 |       };
397 | 
398 |       // 3. Authentication headers (highest priority)
399 |       if (hasBasicAuth()) {
400 |         const base64Credentials = Buffer.from(`${AUTH_BASIC_USERNAME}:${AUTH_BASIC_PASSWORD}`).toString('base64');
401 |         config.headers = {
402 |           ...config.headers,
403 |           'Authorization': `Basic ${base64Credentials}`
404 |         };
405 |       } else if (hasBearerAuth()) {
406 |         config.headers = {
407 |           ...config.headers,
408 |           'Authorization': `Bearer ${AUTH_BEARER}`
409 |         };
410 |       } else if (hasApiKeyAuth()) {
411 |         config.headers = {
412 |           ...config.headers,
413 |           [AUTH_APIKEY_HEADER_NAME as string]: AUTH_APIKEY_VALUE
414 |         };
415 |       }
416 | 
417 |       try {
418 |         const startTime = Date.now();
419 |         const response = await this.axiosInstance.request(config);
420 |         const endTime = Date.now();
421 | 
422 |         // Determine auth method used
423 |         let authMethod = 'none';
424 |         if (hasBasicAuth()) authMethod = 'basic';
425 |         else if (hasBearerAuth()) authMethod = 'bearer';
426 |         else if (hasApiKeyAuth()) authMethod = 'apikey';
427 | 
428 |         // Prepare response object
429 |         const responseObj: ResponseObject = {
430 |           request: {
431 |             url: fullUrl,
432 |             method: config.method || 'GET',
433 |             headers: {
434 |               ...sanitizeHeaders(config.headers as Record<string, string | undefined>, false),
435 |               ...sanitizeHeaders(request.params.arguments.headers || {}, true)
436 |             },
437 |             body: config.data,
438 |             authMethod
439 |           },
440 |           response: {
441 |             statusCode: response.status,
442 |             statusText: response.statusText,
443 |             timing: `${endTime - startTime}ms`,
444 |             headers: sanitizeHeaders(response.headers as Record<string, any>, false),
445 |             body: response.data,
446 |           },
447 |           validation: {
448 |             isError: response.status >= 400,
449 |             messages: response.status >= 400 ? 
450 |               [`Request failed with status ${response.status}`] : 
451 |               ['Request completed successfully']
452 |           }
453 |         };
454 | 
455 |         // Check response body size independently
456 |         const bodyStr = typeof response.data === 'string' 
457 |           ? response.data 
458 |           : JSON.stringify(response.data);
459 |         const bodySize = Buffer.from(bodyStr).length;
460 | 
461 |         if (bodySize > RESPONSE_SIZE_LIMIT) {
462 |           // Simply truncate to the size limit
463 |           responseObj.response.body = bodyStr.slice(0, RESPONSE_SIZE_LIMIT);
464 |           responseObj.validation.messages.push(
465 |             `Response truncated: ${RESPONSE_SIZE_LIMIT} of ${bodySize} bytes returned due to size limit (${RESPONSE_SIZE_LIMIT} bytes)`
466 |           );
467 |           responseObj.validation.truncated = {
468 |             originalSize: bodySize,
469 |             returnedSize: RESPONSE_SIZE_LIMIT,
470 |             truncationPoint: RESPONSE_SIZE_LIMIT,
471 |             sizeLimit: RESPONSE_SIZE_LIMIT
472 |           };
473 |         }
474 | 
475 |         return {
476 |           content: [
477 |             {
478 |               type: 'text',
479 |               text: JSON.stringify(responseObj, null, 2),
480 |             },
481 |           ],
482 |         };
483 |       } catch (error) {
484 |         if (axios.isAxiosError(error)) {
485 |           return {
486 |             content: [
487 |               {
488 |                 type: 'text',
489 |               text: JSON.stringify({
490 |                 error: {
491 |                   message: error.message,
492 |                   code: error.code,
493 |                   request: {
494 |                     url: `${process.env.REST_BASE_URL}${normalizedEndpoint}`,
495 |                     method: config.method,
496 |                     headers: {
497 |                       ...sanitizeHeaders(config.headers as Record<string, string | undefined>, false),
498 |                       ...sanitizeHeaders(request.params.arguments.headers || {}, true)
499 |                     },
500 |                     body: config.data
501 |                   }
502 |                 }
503 |               }, null, 2),
504 |               },
505 |             ],
506 |             isError: true,
507 |           };
508 |         }
509 |         throw error;
510 |       }
511 |     });
512 |   }
513 | 
514 |   async run() {
515 |     await this.setupServer();
516 |     const transport = new StdioServerTransport();
517 |     await this.server.connect(transport);
518 |     console.error('REST API Tester MCP server running on stdio');
519 |   }
520 | }
521 | 
522 | const server = new RestTester();
523 | server.run().catch(console.error);
524 | 
```