# 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 | [](https://mseep.ai/app/dkmaker-mcp-rest-api)
2 |
3 | # MCP REST API Tester
4 | [](https://opensource.org/licenses/MIT)
5 | [](https://www.npmjs.com/package/dkmaker-mcp-rest-api)
6 | [](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 |
```