# 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:
--------------------------------------------------------------------------------
```
node_modules/
build/
src/version.ts
.DS_Store
*.log
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
[](https://mseep.ai/app/dkmaker-mcp-rest-api)
# MCP REST API Tester
[](https://opensource.org/licenses/MIT)
[](https://www.npmjs.com/package/dkmaker-mcp-rest-api)
[](https://smithery.ai/server/dkmaker-mcp-rest-api)
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.
<a href="https://glama.ai/mcp/servers/izr2sp4rqo">
<img width="380" height="200" src="https://glama.ai/mcp/servers/izr2sp4rqo/badge?refresh=1234" />
</a>
## Installation
### Installing via Smithery
To install REST API Tester for Claude Desktop automatically via [Smithery](https://smithery.ai/server/dkmaker-mcp-rest-api):
```bash
npx -y @smithery/cli install dkmaker-mcp-rest-api --client claude
```
### Installing Manually
1. Install the package globally:
```bash
npm install -g dkmaker-mcp-rest-api
```
2. Configure Cline Custom Instructions:
To ensure Cline understands how to effectively use this tool, add the following to your Cline custom instructions (Settings > Custom Instructions):
```markdown
# REST API Testing Instructions
The `test_request` tool enables testing, debugging, and interacting with REST API endpoints. The tool provides comprehensive request/response information and handles authentication automatically.
## When to Use
- Testing specific API endpoints
- Debugging API responses
- Verifying API functionality
- Checking response times
- Validating request/response formats
- Testing local development servers
- Testing API sequences
- Verifying error handling
## Key Features
- Supports GET, POST, PUT, DELETE, PATCH methods
- Handles authentication (Basic, Bearer, API Key)
- Normalizes endpoints automatically
- Provides detailed response information
- Configurable SSL verification and response limits
## Resources
The following resources provide detailed documentation:
- examples: Usage examples and common patterns
- response-format: Response structure and fields
- config: Configuration options and setup guide
Access these resources to understand usage, response formats, and configuration options.
## Important Notes
- Review API implementation for expected behavior
- Handle sensitive data appropriately
- Consider rate limits and API constraints
- Restart server after configuration changes
```
3. Add the server to your MCP configuration:
While these instructions are for Cline, the server should work with any MCP implementation. Configure based on your operating system:
### Windows
⚠️ **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%.
Add to `C:\Users\<YourUsername>\AppData\Roaming\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json`:
```json
{
"mcpServers": {
"rest-api": {
"command": "node",
"args": [
"C:/Users/<YourUsername>/AppData/Roaming/npm/node_modules/dkmaker-mcp-rest-api/build/index.js"
],
"env": {
"REST_BASE_URL": "https://api.example.com",
// Basic Auth
"AUTH_BASIC_USERNAME": "your-username",
"AUTH_BASIC_PASSWORD": "your-password",
// OR Bearer Token
"AUTH_BEARER": "your-token",
// OR API Key
"AUTH_APIKEY_HEADER_NAME": "X-API-Key",
"AUTH_APIKEY_VALUE": "your-api-key",
// SSL Verification (enabled by default)
"REST_ENABLE_SSL_VERIFY": "false", // Set to false to disable SSL verification for self-signed certificates
// Response Size Limit (optional, defaults to 10000 bytes)
"REST_RESPONSE_SIZE_LIMIT": "10000", // Maximum response size in bytes
// Custom Headers (optional)
"HEADER_X-API-Version": "2.0",
"HEADER_Custom-Client": "my-client",
"HEADER_Accept": "application/json"
}
}
}
}
```
### macOS
Add to `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`:
```json
{
"mcpServers": {
"rest-api": {
"command": "npx",
"args": [
"-y",
"dkmaker-mcp-rest-api"
],
"env": {
"REST_BASE_URL": "https://api.example.com",
// Basic Auth
"AUTH_BASIC_USERNAME": "your-username",
"AUTH_BASIC_PASSWORD": "your-password",
// OR Bearer Token
"AUTH_BEARER": "your-token",
// OR API Key
"AUTH_APIKEY_HEADER_NAME": "X-API-Key",
"AUTH_APIKEY_VALUE": "your-api-key",
// SSL Verification (enabled by default)
"REST_ENABLE_SSL_VERIFY": "false", // Set to false to disable SSL verification for self-signed certificates
// Custom Headers (optional)
"HEADER_X-API-Version": "2.0",
"HEADER_Custom-Client": "my-client",
"HEADER_Accept": "application/json"
}
}
}
}
```
Note: Replace the environment variables with your actual values. Only configure one authentication method at a time:
1. Basic Authentication (username/password)
2. Bearer Token (if Basic Auth is not configured)
3. API Key (if neither Basic Auth nor Bearer Token is configured)
## Features
- Test REST API endpoints with different HTTP methods
- Support for GET, POST, PUT, DELETE, and PATCH requests
- Detailed response information including status, headers, and body
- Custom Headers:
- Global headers via HEADER_* environment variables
- Case-insensitive prefix (HEADER_, header_, HeAdEr_)
- Case preservation for header names
- Priority-based application (per-request > auth > custom)
- Request body handling for POST/PUT methods
- Response Size Management:
- Automatic response size limiting (default: 10KB/10000 bytes)
- Configurable size limit via REST_RESPONSE_SIZE_LIMIT environment variable
- Clear truncation metadata when responses exceed limit
- Preserves response structure while only truncating body content
- SSL Certificate Verification:
- Enabled by default for secure operation
- Can be disabled for self-signed certificates or development environments
- Control via REST_ENABLE_SSL_VERIFY environment variable
- Multiple authentication methods:
- Basic Authentication (username/password)
- Bearer Token Authentication
- API Key Authentication (custom header)
## Usage Examples
Once installed and configured, you can use the REST API Tester through Cline to test your API endpoints:
```typescript
// Test a GET endpoint
use_mcp_tool('rest-api', 'test_request', {
"method": "GET",
"endpoint": "/users"
});
// Test a POST endpoint with body
use_mcp_tool('rest-api', 'test_request', {
"method": "POST",
"endpoint": "/users",
"body": {
"name": "John Doe",
"email": "[email protected]"
}
});
// Test with custom headers
use_mcp_tool('rest-api', 'test_request', {
"method": "GET",
"endpoint": "/products",
"headers": {
"Accept-Language": "en-US",
"X-Custom-Header": "custom-value"
}
});
```
## Development
1. Clone the repository:
```bash
git clone https://github.com/zenturacp/mcp-rest-api.git
cd mcp-rest-api
```
2. Install dependencies:
```bash
npm install
```
3. Build the project:
```bash
npm run build
```
For development with auto-rebuild:
```bash
npm run watch
```
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/.github/workflows/refresh-badges.yml:
--------------------------------------------------------------------------------
```yaml
name: Refresh Badges
on:
schedule:
- cron: '0 0 * * *' # Run at 00:00 UTC every day
push:
branches:
- main
workflow_dispatch: # Allow manual triggering
jobs:
refresh:
runs-on: ubuntu-latest
steps:
- name: Refresh badges
uses: b3b00/[email protected]
with:
repository: 'zenturacp/mcp-rest-api'
branch: 'main'
```
--------------------------------------------------------------------------------
/.github/conventional-changelog.config.js:
--------------------------------------------------------------------------------
```javascript
module.exports = {
types: [
{ type: 'feat', section: 'Features' },
{ type: 'fix', section: 'Bug Fixes' },
{ type: 'chore', section: 'Maintenance' },
{ type: 'docs', section: 'Documentation' },
{ type: 'style', section: 'Styling' },
{ type: 'refactor', section: 'Code Refactoring' },
{ type: 'perf', section: 'Performance' },
{ type: 'test', section: 'Testing' },
{ type: 'ci', section: 'CI/CD' },
{ type: 'build', section: 'Build System' }
]
};
```
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
```javascript
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'build',
'chore',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'revert',
'style',
'test'
]
],
'type-case': [2, 'always', 'lower-case'],
'type-empty': [2, 'never'],
'scope-case': [2, 'always', 'lower-case'],
'subject-case': [2, 'always', 'lower-case'],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.']
}
}
```
--------------------------------------------------------------------------------
/.github/workflows/pr-validation.yml:
--------------------------------------------------------------------------------
```yaml
name: Pull Request Validation
on:
pull_request:
branches: [ main ]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Validate Conventional Commits
run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose
- name: Build
run: npm run build
- name: Run Tests
run: |
if [ -f "package.json" ] && grep -q "\"test\":" "package.json"; then
npm test
else
echo "No test script found in package.json"
fi
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
# Use the Node.js image with the required version for the project
FROM node:18-alpine AS builder
# Set working directory
WORKDIR /app
# Copy package files to the working directory
COPY package.json package-lock.json ./
# Install dependencies
RUN npm install
# Copy all files to the working directory
COPY . .
# Build the TypeScript files
RUN npm run build
# Create the final release image
FROM node:18-alpine AS release
# Set working directory
WORKDIR /app
# Copy built files and necessary package information
COPY --from=builder /app/build /app/build
COPY --from=builder /app/package.json /app/package-lock.json /app/node_modules ./
# Environment configuration for runtime (configured externally)
ENV REST_BASE_URL=""
ENV AUTH_BASIC_USERNAME=""
ENV AUTH_BASIC_PASSWORD=""
ENV AUTH_BEARER=""
ENV AUTH_APIKEY_HEADER_NAME=""
ENV AUTH_APIKEY_VALUE=""
ENV REST_ENABLE_SSL_VERIFY="true"
ENV REST_RESPONSE_SIZE_LIMIT="10000"
# Command to run the server
ENTRYPOINT ["node", "build/index.js"]
```
--------------------------------------------------------------------------------
/.github/conventional-changelog.config.cjs:
--------------------------------------------------------------------------------
```
module.exports = {
types: [
{ type: 'feat', section: 'Features', hidden: false },
{ type: 'fix', section: 'Bug Fixes', hidden: false },
{ type: 'chore', section: 'Maintenance', hidden: false },
{ type: 'docs', section: 'Documentation', hidden: false },
{ type: 'style', section: 'Styling', hidden: false },
{ type: 'refactor', section: 'Code Refactoring', hidden: false },
{ type: 'perf', section: 'Performance', hidden: false },
{ type: 'test', section: 'Testing', hidden: false },
{ type: 'ci', section: 'CI/CD', hidden: false },
{ type: 'build', section: 'Build System', hidden: false }
],
releaseRules: [
{ type: 'feat', release: 'minor' },
{ type: 'fix', release: 'patch' },
{ type: 'perf', release: 'patch' },
{ type: 'chore', release: 'patch' },
{ type: 'docs', release: 'patch' },
{ type: 'style', release: 'patch' },
{ type: 'refactor', release: 'patch' },
{ type: 'test', release: 'patch' },
{ type: 'ci', release: 'patch' },
{ type: 'build', release: 'patch' }
]
};
```
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
## [0.4.0](https://github.com/dkmaker/mcp-rest-api/compare/v0.3.0...v0.4.0) (2025-01-08)
### Features
* add custom header support ([9a48e0d](https://github.com/dkmaker/mcp-rest-api/commit/9a48e0d794a743f7a62c7cb73d6f5b1be9e44107))
## [0.3.0](https://github.com/dkmaker/mcp-rest-api/compare/v0.2.0...v0.3.0) (2024-12-28)
### Features
* add config documentation and improve URL resolution examples ([8c6100f](https://github.com/dkmaker/mcp-rest-api/commit/8c6100f47777605a0571edbd161ffd20fc48b640))
* add MCP resources for documentation ([a20cf35](https://github.com/dkmaker/mcp-rest-api/commit/a20cf352e9731841a8d7e833007a96bdd1a0c390))
### Bug Fixes
* correct response truncation to return first N bytes ([ce34649](https://github.com/dkmaker/mcp-rest-api/commit/ce34649c4d8e6bc6d740e8f3fbc6e3df517e0eec))
## [0.2.0](https://github.com/dkmaker/mcp-rest-api/compare/0fdbe844dd4ce8b79f38a33df323a29e28253724...v0.2.0) (2024-12-21)
### Features
* **ssl:** add SSL verification control with secure defaults ([0fdbe84](https://github.com/dkmaker/mcp-rest-api/commit/0fdbe844dd4ce8b79f38a33df323a29e28253724))
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "dkmaker-mcp-rest-api",
"version": "0.4.0",
"description": "A generic REST API tester for testing HTTP endpoints",
"license": "MIT",
"type": "module",
"bin": {
"dkmaker-mcp-rest-api": "./build/index.js"
},
"files": [
"build",
"README.md",
"LICENSE"
],
"scripts": {
"prebuild": "node scripts/build.js",
"build": "tsc",
"prepare": "npm run build",
"watch": "tsc --watch",
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"axios": "^1.7.9"
},
"devDependencies": {
"@commitlint/cli": "^19.6.1",
"@commitlint/config-conventional": "^19.6.0",
"@types/node": "^20.11.24",
"typescript": "^5.3.3"
},
"keywords": [
"mcp",
"rest",
"api",
"http",
"testing",
"cline",
"development",
"typescript"
],
"author": "zenturacp",
"repository": {
"type": "git",
"url": "git+https://github.com/dkmaker/mcp-rest-api.git"
},
"bugs": {
"url": "https://github.com/dkmaker/mcp-rest-api/issues"
},
"homepage": "https://github.com/dkmaker/mcp-rest-api#readme",
"engines": {
"node": ">=18.0.0"
}
}
```
--------------------------------------------------------------------------------
/src/resources/response-format.md:
--------------------------------------------------------------------------------
```markdown
# Response Format Documentation
The REST API testing tool returns a comprehensive JSON response containing request details, response information, and validation results.
## Response Structure
```json
{
"request": {
"url": "http://api.example.com/users",
"method": "GET",
"headers": {},
"body": null,
"authMethod": "none"
},
"response": {
"statusCode": 200,
"statusText": "OK",
"timing": "123ms",
"headers": {},
"body": {}
},
"validation": {
"isError": false,
"messages": ["Request completed successfully"]
}
}
```
## Response Fields
### Request Details
- `url`: Full URL including base URL and endpoint
- `method`: HTTP method used
- `headers`: Request headers sent
- `body`: Request body (if applicable)
- `authMethod`: Authentication method used (none, basic, bearer, or apikey)
### Response Details
- `statusCode`: HTTP status code
- `statusText`: Status message
- `timing`: Request duration in milliseconds
- `headers`: Response headers received
- `body`: Response body content
### Validation
- `isError`: true if status code >= 400
- `messages`: Array of validation or error messages
## Error Response Example
```json
{
"error": {
"message": "Connection refused",
"code": "ECONNREFUSED",
"request": {
"url": "http://api.example.com/users",
"method": "GET",
"headers": {},
"body": null
}
}
}
```
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env node
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function main() {
const pkg = JSON.parse(
await fs.readFile(
path.join(__dirname, '..', 'package.json'),
'utf8'
)
);
const versionPath = path.join(__dirname, '..', 'src', 'version.ts');
// Always generate version.ts with actual values during build
const content = `// Auto-generated by build script
export const VERSION = '${pkg.version}';
export const PACKAGE_NAME = '${pkg.name}';
export const SERVER_NAME = '${pkg.name.split('-').slice(-2).join('-')}';
`;
await fs.writeFile(versionPath, content);
console.log('Generated version.ts with package values');
// Copy resources to build directory
const resourcesSrcDir = path.join(__dirname, '..', 'src', 'resources');
const resourcesBuildDir = path.join(__dirname, '..', 'build', 'resources');
try {
await fs.mkdir(resourcesBuildDir, { recursive: true });
const files = await fs.readdir(resourcesSrcDir);
for (const file of files) {
await fs.copyFile(
path.join(resourcesSrcDir, file),
path.join(resourcesBuildDir, file)
);
}
console.log('Copied resources to build directory');
} catch (error) {
console.error('Error copying resources:', error);
throw error;
}
}
main().catch(console.error);
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
startCommand:
type: stdio
configSchema:
# JSON Schema defining the configuration options for the MCP.
type: object
required:
- restBaseUrl
properties:
restBaseUrl:
type: string
description: The base URL for the REST API.
authBasicUsername:
type: string
description: The username for Basic Authentication.
authBasicPassword:
type: string
description: The password for Basic Authentication.
authBearer:
type: string
description: The bearer token for authentication.
authApiKeyHeaderName:
type: string
description: The header name for API Key Authentication.
authApiKeyValue:
type: string
description: The API key value for API Key Authentication.
restEnableSslVerify:
type: boolean
default: true
description: Enable or disable SSL verification.
restResponseSizeLimit:
type: number
default: 10000
description: The maximum response size limit in bytes.
commandFunction:
# A function that produces the CLI command to start the MCP on stdio.
|-
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
name: Publish Package
on:
push:
branches: [ main ]
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18.x'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Conventional Changelog Action
id: changelog
uses: TriPSs/conventional-changelog-action@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
git-message: 'chore(release): {version}'
config-file-path: '.github/conventional-changelog.config.cjs'
tag-prefix: 'v'
output-file: 'CHANGELOG.md'
skip-version-file: false
skip-commit: false
skip-on-empty: false
git-user-name: ${{ secrets.CHANGELOG_GIT_NAME }}
git-user-email: ${{ secrets.CHANGELOG_GIT_EMAIL }}
- name: Build
run: npm run build
- name: Create Release
if: steps.changelog.outputs.skipped == 'false'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create v${{ steps.changelog.outputs.version }} \
--title "Release v${{ steps.changelog.outputs.version }}" \
--notes "${{ steps.changelog.outputs.clean_changelog }}"
- name: Publish to NPM
if: steps.changelog.outputs.skipped == 'false'
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
--------------------------------------------------------------------------------
/src/resources/config.md:
--------------------------------------------------------------------------------
```markdown
# REST API Tester Configuration
This document describes all available configuration options for the REST API testing tool.
## Core Configuration
### REST_BASE_URL (Required)
- Description: The base URL that all endpoint paths will be resolved against
- Example: `http://localhost:3000` or `https://api.example.com`
- 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`
### REST_RESPONSE_SIZE_LIMIT (Optional)
- Description: Maximum size in bytes for response data
- Default: 10000 (10KB)
- Example: `50000` for 50KB limit
- 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
### REST_ENABLE_SSL_VERIFY (Optional)
- Description: Controls SSL certificate verification
- Default: `true`
- Values: Set to `false` to disable SSL verification for self-signed certificates
- Usage: Disable when testing APIs with self-signed certificates in development environments
## Custom Headers Configuration
### Custom Headers (Optional)
- Description: Add custom headers to all requests using environment variables
- Pattern: `HEADER_<HeaderName>=<Value>` (prefix is case-insensitive)
- Examples:
```bash
HEADER_X-API-Version=2.0
header_Custom-Client=my-client
HeAdEr_Accept=application/json
```
- Usage: Headers are added to all requests. The header name after `HEADER_` preserves its exact case
- Priority: Per-request headers > Authentication headers > Custom global headers
## Authentication Configuration
The tool supports three authentication methods. Configure one based on your API's requirements.
### Basic Authentication
- REST_BASIC_USERNAME: Username for Basic Auth
- REST_BASIC_PASSWORD: Password for Basic Auth
- Usage: When both are set, requests will include Basic Auth header
### Bearer Token
- REST_BEARER: Bearer token value
- Usage: When set, requests will include `Authorization: Bearer <token>` header
### API Key
- REST_APIKEY_HEADER_NAME: Name of the header for API key
- REST_APIKEY_VALUE: Value of the API key
- Example:
```
REST_APIKEY_HEADER_NAME=X-API-Key
REST_APIKEY_VALUE=your-api-key-here
```
- Usage: When both are set, requests will include the specified header with the API key
## Configuration Examples
### Local Development
```bash
REST_BASE_URL=http://localhost:3000
REST_ENABLE_SSL_VERIFY=false
REST_RESPONSE_SIZE_LIMIT=50000
```
### Production API with Bearer Token
```bash
REST_BASE_URL=https://api.example.com
REST_BEARER=your-bearer-token
```
### API with Basic Auth
```bash
REST_BASE_URL=https://api.example.com
REST_BASIC_USERNAME=admin
REST_BASIC_PASSWORD=secret
```
### API with API Key
```bash
REST_BASE_URL=https://api.example.com
REST_APIKEY_HEADER_NAME=X-API-Key
REST_APIKEY_VALUE=your-api-key
```
### API with Custom Headers
```bash
REST_BASE_URL=https://api.example.com
HEADER_X-API-Version=2.0
HEADER_Custom-Client=my-client
HEADER_Accept=application/json
```
## Changing Configuration
Configuration can be updated by:
1. Setting environment variables before starting the server
2. Modifying the MCP server configuration file
3. Using environment variable commands in your terminal
Remember to restart the server after changing configuration for the changes to take effect.
```
--------------------------------------------------------------------------------
/src/resources/examples.md:
--------------------------------------------------------------------------------
```markdown
# REST API Testing Examples
⚠️ IMPORTANT:
- 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.
- 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).
For example, if the base URL is `http://localhost:3000`:
✅ Correct: `"/api/users"` → Resolves to: `http://localhost:3000/api/users`
❌ Incorrect: `"http://localhost:3000/api/users"` or `"www.example.com/api/users"`
If you use a `host` argument:
✅ Correct: `"host": "https://api.example.com/v1", "endpoint": "/users"` → Resolves to: `https://api.example.com/v1/users`
## Basic GET Request
```typescript
use_mcp_tool('rest-api', 'test_request', {
"method": "GET",
"endpoint": "/users" // Will be appended to REST_BASE_URL or 'host' if provided
});
```
## GET with Query Parameters
```typescript
use_mcp_tool('rest-api', 'test_request', {
"method": "GET",
"endpoint": "/users?role=admin&status=active" // Always a path, not a full URL
});
```
## POST Request with Body
```typescript
use_mcp_tool('rest-api', 'test_request', {
"method": "POST",
"endpoint": "/users",
"body": {
"name": "John Doe",
"email": "[email protected]"
}
});
```
## Request with Custom Headers
```typescript
use_mcp_tool('rest-api', 'test_request', {
"method": "GET",
"endpoint": "/secure-resource",
"headers": {
"Custom-Header": "value",
"Another-Header": "another-value"
}
});
```
## PUT Request Example
```typescript
use_mcp_tool('rest-api', 'test_request', {
"method": "PUT",
"endpoint": "/users/123",
"body": {
"name": "Updated Name",
"status": "inactive"
}
});
```
## DELETE Request Example
```typescript
use_mcp_tool('rest-api', 'test_request', {
"method": "DELETE",
"endpoint": "/users/123"
});
```
## PATCH Request Example
```typescript
use_mcp_tool('rest-api', 'test_request', {
"method": "PATCH",
"endpoint": "/users/123",
"body": {
"status": "active"
}
});
```
## Using the Optional `host` Argument
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).
```typescript
use_mcp_tool('rest-api', 'test_request', {
"method": "GET",
"endpoint": "/users",
"host": "https://api.example.com/v1" // The request will go to https://api.example.com/v1/users
});
```
- The `host` argument must include the protocol (http or https).
- If a path is included, any trailing slash will be removed.
- If `host` is invalid, you will receive a clear error message.
- The `endpoint` argument must always be a path, never a full URL.
## Changing Base URL
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.
Example:
```bash
# Instead of this:
❌ "endpoint": "https://api.example.com/users" # Wrong - don't include the full URL
# Do this:
# 1. Update the base URL configuration to: https://api.example.com
# 2. Then use just the path:
✅ "endpoint": "/users" # This will resolve to: https://api.example.com/users
# Or, for a single request:
✅ "host": "https://api.example.com", "endpoint": "/users" # This will resolve to: https://api.example.com/users
```
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import axios, { AxiosInstance, AxiosRequestConfig, Method } from 'axios';
import { VERSION, SERVER_NAME } from './version.js';
if (!process.env.REST_BASE_URL) {
throw new Error('REST_BASE_URL environment variable is required');
}
// Default response size limit: 10KB (10000 bytes)
const RESPONSE_SIZE_LIMIT = process.env.REST_RESPONSE_SIZE_LIMIT
? parseInt(process.env.REST_RESPONSE_SIZE_LIMIT, 10)
: 10000;
if (isNaN(RESPONSE_SIZE_LIMIT) || RESPONSE_SIZE_LIMIT <= 0) {
throw new Error('REST_RESPONSE_SIZE_LIMIT must be a positive number');
}
const AUTH_BASIC_USERNAME = process.env.AUTH_BASIC_USERNAME;
const AUTH_BASIC_PASSWORD = process.env.AUTH_BASIC_PASSWORD;
const AUTH_BEARER = process.env.AUTH_BEARER;
const AUTH_APIKEY_HEADER_NAME = process.env.AUTH_APIKEY_HEADER_NAME;
const AUTH_APIKEY_VALUE = process.env.AUTH_APIKEY_VALUE;
const REST_ENABLE_SSL_VERIFY = process.env.REST_ENABLE_SSL_VERIFY !== 'false';
interface EndpointArgs {
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
endpoint: string;
body?: any;
headers?: Record<string, string>;
host?: string;
}
interface ValidationResult {
isError: boolean;
messages: string[];
truncated?: {
originalSize: number;
returnedSize: number;
truncationPoint: number;
sizeLimit: number;
};
}
// Function to sanitize headers by removing sensitive values and non-approved headers
const sanitizeHeaders = (
headers: Record<string, any>,
isFromOptionalParams: boolean = false
): Record<string, any> => {
const sanitized: Record<string, any> = {};
for (const [key, value] of Object.entries(headers)) {
const lowerKey = key.toLowerCase();
// Always include headers from optional parameters
if (isFromOptionalParams) {
sanitized[key] = value;
continue;
}
// Handle authentication headers
if (
lowerKey === 'authorization' ||
(AUTH_APIKEY_HEADER_NAME && lowerKey === AUTH_APIKEY_HEADER_NAME.toLowerCase())
) {
sanitized[key] = '[REDACTED]';
continue;
}
// For headers from config/env
const customHeaders = getCustomHeaders();
if (key in customHeaders) {
// Show value only for headers that are in the approved list
const safeHeaders = new Set([
'accept',
'accept-language',
'content-type',
'user-agent',
'cache-control',
'if-match',
'if-none-match',
'if-modified-since',
'if-unmodified-since'
]);
const lowerKey = key.toLowerCase();
sanitized[key] = safeHeaders.has(lowerKey) ? value : '[REDACTED]';
}
}
return sanitized;
};
interface ResponseObject {
request: {
url: string;
method: string;
headers: Record<string, string | undefined>;
body: any;
authMethod: string;
};
response: {
statusCode: number;
statusText: string;
timing: string;
headers: Record<string, any>;
body: any;
};
validation: ValidationResult;
}
const normalizeBaseUrl = (url: string): string => url.replace(/\/+$/, '');
const isValidEndpointArgs = (args: any): args is EndpointArgs => {
if (typeof args !== 'object' || args === null) return false;
if (!['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(args.method)) return false;
if (typeof args.endpoint !== 'string') return false;
if (args.headers !== undefined && (typeof args.headers !== 'object' || args.headers === null)) return false;
// Check if endpoint contains a full URL
const urlPattern = /^(https?:\/\/|www\.)/i;
if (urlPattern.test(args.endpoint)) {
throw new McpError(
ErrorCode.InvalidParams,
`Invalid endpoint format. Do not include full URLs. Instead of "${args.endpoint}", use just the path (e.g. "/api/users"). ` +
`Your path will be resolved to: ${process.env.REST_BASE_URL}${args.endpoint.replace(/^\/+|\/+$/g, '')}. ` +
`To test a different base URL, update the REST_BASE_URL environment variable.`
);
}
// Validate .host if present
if (args.host !== undefined) {
try {
const url = new URL(args.host);
if (!/^https?:$/.test(url.protocol)) {
throw new Error();
}
// Remove trailing slash if present
if (url.pathname.endsWith('/') && url.pathname !== '/') {
url.pathname = url.pathname.replace(/\/+$/, '');
args.host = url.origin + url.pathname;
} else {
args.host = url.origin + url.pathname;
}
} catch (e) {
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}"`);
}
}
return true;
};
const hasBasicAuth = () => AUTH_BASIC_USERNAME && AUTH_BASIC_PASSWORD;
const hasBearerAuth = () => !!AUTH_BEARER;
const hasApiKeyAuth = () => AUTH_APIKEY_HEADER_NAME && AUTH_APIKEY_VALUE;
// Collect custom headers from environment variables
const getCustomHeaders = (): Record<string, string> => {
const headers: Record<string, string> = {};
const headerPrefix = /^header_/i; // Case-insensitive match for 'header_'
for (const [key, value] of Object.entries(process.env)) {
if (headerPrefix.test(key) && value !== undefined) {
// Extract header name after the prefix, preserving case
const headerName = key.replace(headerPrefix, '');
headers[headerName] = value;
}
}
return headers;
};
class RestTester {
private server!: Server;
private axiosInstance!: AxiosInstance;
constructor() {
this.setupServer();
}
private async setupServer() {
this.server = new Server(
{
name: SERVER_NAME,
version: VERSION,
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
const https = await import('https');
this.axiosInstance = axios.create({
baseURL: normalizeBaseUrl(process.env.REST_BASE_URL!),
validateStatus: () => true, // Allow any status code
httpsAgent: REST_ENABLE_SSL_VERIFY ? undefined : new https.Agent({ // Disable SSL verification only when explicitly set to false
rejectUnauthorized: false
})
});
this.setupToolHandlers();
this.setupResourceHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private setupResourceHandlers() {
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: `${SERVER_NAME}://examples`,
name: 'REST API Usage Examples',
description: 'Detailed examples of using the REST API testing tool',
mimeType: 'text/markdown'
},
{
uri: `${SERVER_NAME}://response-format`,
name: 'Response Format Documentation',
description: 'Documentation of the response format and structure',
mimeType: 'text/markdown'
},
{
uri: `${SERVER_NAME}://config`,
name: 'Configuration Documentation',
description: 'Documentation of all configuration options and how to use them',
mimeType: 'text/markdown'
}
]
}));
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const uriPattern = new RegExp(`^${SERVER_NAME}://(.+)$`);
const match = request.params.uri.match(uriPattern);
if (!match) {
throw new McpError(
ErrorCode.InvalidRequest,
`Invalid resource URI format: ${request.params.uri}`
);
}
const resource = match[1];
const fs = await import('fs');
const path = await import('path');
try {
const url = await import('url');
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// In the built app, resources are in build/resources
// In development, they're in src/resources
const resourcePath = path.join(__dirname, 'resources', `${resource}.md`);
const content = await fs.promises.readFile(resourcePath, 'utf8');
return {
contents: [{
uri: request.params.uri,
mimeType: 'text/markdown',
text: content
}]
};
} catch (error) {
throw new McpError(
ErrorCode.InvalidRequest,
`Resource not found: ${resource}`
);
}
});
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'test_request',
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: ${
hasBasicAuth() ?
`Basic Auth with username: ${AUTH_BASIC_USERNAME}` :
hasBearerAuth() ?
'Bearer token authentication configured' :
hasApiKeyAuth() ?
`API Key using header: ${AUTH_APIKEY_HEADER_NAME}` :
'No authentication configured'
} | ${(() => {
const customHeaders = getCustomHeaders();
if (Object.keys(customHeaders).length === 0) {
return 'No custom headers defined (see config resource for headers)';
}
// List of common headers that are safe to show values for
const safeHeaders = new Set([
'accept',
'accept-language',
'content-type',
'user-agent',
'cache-control',
'if-match',
'if-none-match',
'if-modified-since',
'if-unmodified-since'
]);
const headerList = Object.entries(customHeaders).map(([name, value]) => {
const lowerName = name.toLowerCase();
return safeHeaders.has(lowerName) ?
`${name}(${value})` :
name;
}).join(', ');
return `Custom headers defined: ${headerList} (see config resource for headers)`;
})()} | 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.
`,
inputSchema: {
type: 'object',
properties: {
method: {
type: 'string',
enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
description: 'HTTP method to use',
},
endpoint: {
type: 'string',
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"`,
},
body: {
type: 'object',
description: 'Optional request body for POST/PUT requests',
},
headers: {
type: 'object',
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.',
additionalProperties: {
type: 'string'
}
}
},
required: ['method', 'endpoint'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== 'test_request') {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
if (!isValidEndpointArgs(request.params.arguments)) {
throw new McpError(
ErrorCode.InvalidParams,
'Invalid test endpoint arguments'
);
}
// Ensure endpoint starts with / and remove any trailing slashes
const normalizedEndpoint = `/${request.params.arguments.endpoint.replace(/^\/+|\/+$/g, '')}`;
const fullUrl = `${request.params.arguments.host || process.env.REST_BASE_URL}${normalizedEndpoint}`;
// Initialize request config
const config: AxiosRequestConfig = {
method: request.params.arguments.method as Method,
url: fullUrl,
headers: {},
};
// Add request body for POST/PUT/PATCH
if (['POST', 'PUT', 'PATCH'].includes(request.params.arguments.method) && request.params.arguments.body) {
config.data = request.params.arguments.body;
}
// Apply headers in order of priority (lowest to highest)
// 1. Custom global headers (lowest priority)
const customHeaders = getCustomHeaders();
config.headers = {
...customHeaders,
...config.headers,
...(request.params.arguments.headers || {}) // Request-specific headers (middle priority)
};
// 3. Authentication headers (highest priority)
if (hasBasicAuth()) {
const base64Credentials = Buffer.from(`${AUTH_BASIC_USERNAME}:${AUTH_BASIC_PASSWORD}`).toString('base64');
config.headers = {
...config.headers,
'Authorization': `Basic ${base64Credentials}`
};
} else if (hasBearerAuth()) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${AUTH_BEARER}`
};
} else if (hasApiKeyAuth()) {
config.headers = {
...config.headers,
[AUTH_APIKEY_HEADER_NAME as string]: AUTH_APIKEY_VALUE
};
}
try {
const startTime = Date.now();
const response = await this.axiosInstance.request(config);
const endTime = Date.now();
// Determine auth method used
let authMethod = 'none';
if (hasBasicAuth()) authMethod = 'basic';
else if (hasBearerAuth()) authMethod = 'bearer';
else if (hasApiKeyAuth()) authMethod = 'apikey';
// Prepare response object
const responseObj: ResponseObject = {
request: {
url: fullUrl,
method: config.method || 'GET',
headers: {
...sanitizeHeaders(config.headers as Record<string, string | undefined>, false),
...sanitizeHeaders(request.params.arguments.headers || {}, true)
},
body: config.data,
authMethod
},
response: {
statusCode: response.status,
statusText: response.statusText,
timing: `${endTime - startTime}ms`,
headers: sanitizeHeaders(response.headers as Record<string, any>, false),
body: response.data,
},
validation: {
isError: response.status >= 400,
messages: response.status >= 400 ?
[`Request failed with status ${response.status}`] :
['Request completed successfully']
}
};
// Check response body size independently
const bodyStr = typeof response.data === 'string'
? response.data
: JSON.stringify(response.data);
const bodySize = Buffer.from(bodyStr).length;
if (bodySize > RESPONSE_SIZE_LIMIT) {
// Simply truncate to the size limit
responseObj.response.body = bodyStr.slice(0, RESPONSE_SIZE_LIMIT);
responseObj.validation.messages.push(
`Response truncated: ${RESPONSE_SIZE_LIMIT} of ${bodySize} bytes returned due to size limit (${RESPONSE_SIZE_LIMIT} bytes)`
);
responseObj.validation.truncated = {
originalSize: bodySize,
returnedSize: RESPONSE_SIZE_LIMIT,
truncationPoint: RESPONSE_SIZE_LIMIT,
sizeLimit: RESPONSE_SIZE_LIMIT
};
}
return {
content: [
{
type: 'text',
text: JSON.stringify(responseObj, null, 2),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: {
message: error.message,
code: error.code,
request: {
url: `${process.env.REST_BASE_URL}${normalizedEndpoint}`,
method: config.method,
headers: {
...sanitizeHeaders(config.headers as Record<string, string | undefined>, false),
...sanitizeHeaders(request.params.arguments.headers || {}, true)
},
body: config.data
}
}
}, null, 2),
},
],
isError: true,
};
}
throw error;
}
});
}
async run() {
await this.setupServer();
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('REST API Tester MCP server running on stdio');
}
}
const server = new RestTester();
server.run().catch(console.error);
```