Email Verification MCP Server
by 05tanish
README.md
# Email Verification MCP Server
An MCP (Model Context Protocol) server that exposes a `verify_email` tool for AI agents and MCP clients to verify email addresses through a stxructured, type-safe interface.
## 1. Project Overview
This project implements an MCP server that wraps an email verification service (InboxValid) behind a clean tool interface. An AI agent or MCP client calls `verify_email(address)` and receives a structured response indicating whether the address is **valid**, **invalid**, **risky**, or encountered an **error**.
A mock InboxValid API is included so the entire system is self-contained and runnable without external dependencies.
## 2. Architecture
```text
MCP Client / AI Agent
│
▼
┌─────────────────────┐
│ MCP Server │ ← Exposes verify_email tool via stdio
│ (src/server.ts) │
└────────┬────────────┘
│
▼
┌─────────────────────────┐
│ InboxValid Client │ ← HTTP client with retry/backoff
│ (services/ │
│ inboxValidClient.ts) │
└────────┬────────────────┘
│ HTTP POST /verify
▼
┌─────────────────────────┐
│ Mock InboxValid API │ ← Express.js server with deterministic rules
│ (mock-api/server.ts) │
└─────────────────────────┘
```
**Separation of concerns:**
| Layer | Responsibility |
|-------|----------------|
| `src/server.ts` | MCP protocol, tool registration |
| `src/tools/verifyEmail.ts` | Input validation, orchestration |
| `src/services/inboxValidClient.ts` | HTTP communication, retry logic |
| `src/schemas/email.ts` | Zod schemas, TypeScript types |
| `mock-api/server.ts` | Simulated external API |
## 3. Tech Stack
| Technology | Purpose |
|------------|---------|
| **Node.js** | Runtime environment |
| **TypeScript** | Type safety with strict mode |
| **MCP SDK** | Model Context Protocol server implementation |
| **Zod** | Runtime input/output validation |
| **Express.js** | Mock InboxValid API server |
| **Vitest** | Fast, modern test framework |
| **Docker** | Containerized deployment |
## 4. Installation
```bash
git clone <repository-url>
cd email-verification-mcp
npm install
npm run build
```
## 5. Running Locally
### Start the Mock API
```bash
# Development (with hot reload)
npm run dev:mock-api
# Production (after build)
npm run start:mock-api
```
The mock API starts on `http://localhost:3001`.
### Start the MCP Server
In a separate terminal:
```bash
# Development
INBOXVALID_API_URL=http://localhost:3001 npm run dev:mcp
# Production (after build)
INBOXVALID_API_URL=http://localhost:3001 npm run start:mcp
```
The MCP server communicates over **stdio** (standard input/output), as required by the MCP protocol.
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `INBOXVALID_API_URL` | `http://localhost:3001` | Base URL of the InboxValid API |
| `PORT` | `3000` | (Reserved for future HTTP transport) |
| `MOCK_API_PORT` | `3001` | Port for the mock API server |
Copy `.env.example` to `.env` and adjust as needed.
## 6. MCP Tool
### `verify_email`
**Description:** Verify whether an email address is valid, invalid, or risky using the InboxValid verification service.
**Input:**
```json
{
"address": "user@example.com"
}
```
**Output (success):**
```json
{
"address": "user@gmail.com",
"status": "valid",
"reason": "Mailbox appears deliverable"
}
```
**Output (error):**
```json
{
"address": "user@example.com",
"status": "error",
"reason": "Email verification service temporarily unavailable"
}
```
**Statuses:**
| Status | Meaning |
|--------|---------|
| `valid` | Mailbox exists and is deliverable |
| `invalid` | Domain or mailbox does not exist |
| `risky` | Temporary or disposable email detected |
| `error` | Verification service failure |
## 7. Error Handling
| Scenario | Behavior |
|----------|----------|
| **Malformed email** | Zod validation rejects it immediately; returns `status: "error"` |
| **Mock API returns 4xx** | Non-retryable; returns `status: "error"` with the validation message |
| **Mock API returns 5xx** | Retries up to 3 times with exponential backoff |
| **Network error / timeout** | Retries up to 3 times with exponential backoff |
| **Unexpected API response** | Treated as an integration error; returns `status: "error"` |
| **All retries exhausted** | Returns `status: "error"` with "temporarily unavailable" message |
Raw stack traces are **never** exposed through the MCP tool.
## 8. Retry Strategy
Transient failures (network errors, timeouts, HTTP 5xx) trigger automatic retries:
```text
Attempt 1 → immediate
Attempt 2 → 500ms delay
Attempt 3 → 1000ms delay
```
- **Maximum attempts:** 3
- **Backoff:** Linear (500ms × attempt number)
- **Non-retryable:** 4xx client errors are returned immediately
- **Request timeout:** 5 seconds per attempt
## 9. Testing
Run the full test suite:
```bash
npm test
```
Run tests in watch mode during development:
```bash
npm run test:watch
```
### Test Coverage
| Test | Description |
|------|-------------|
| Valid email | `user@gmail.com` → `valid` |
| Invalid email | `@invalid-domain.test` → `invalid` |
| Risky email (temp) | `temp@example.com` → `risky` |
| Risky email (disposable) | `disposable@example.com` → `risky` |
| Malformed email | `not-an-email` → input validation error |
| Missing address | `{}` → input validation error |
| Retry success | 1 failure then success → `valid` |
| Retry exhaustion | 5 failures → `error` |
| Unreachable service | Bad port → `error` |
| Timeout handling | 1ms timeout → structured error |
## 10. Docker
### Build and Run
```bash
docker compose up --build
```
This starts two services:
| Service | Role | Port |
|---------|------|------|
| `mock-api` | Mock InboxValid API | 3001 |
| `mcp-server` | MCP Server (stdio) | — |
The MCP server waits for the mock API health check before starting.
### Stop
```bash
docker compose down
```
## 11. Demo
### GUI — MCP Inspector
The easiest way to interact with the server is the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) web UI.
```bash
INBOXVALID_API_URL=http://localhost:3001 npx -y @modelcontextprotocol/inspector npx tsx src/server.ts
```
#### Server Connected

#### Valid Email Result

#### Invalid Email Result

---
### CLI — Pipe JSON-RPC via stdio
You can also test entirely from the command line by piping JSON-RPC messages into the MCP server:
#### Valid Email
```bash
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"verify_email","arguments":{"address":"user@gmail.com"}}}' | \
INBOXVALID_API_URL=http://localhost:3001 npx tsx src/server.ts
```
#### Invalid Email
```bash
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"verify_email","arguments":{"address":"test@invalid-domain.test"}}}' | \
INBOXVALID_API_URL=http://localhost:3001 npx tsx src/server.ts
```
#### Risky Email
```bash
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"verify_email","arguments":{"address":"temp@example.com"}}}' | \
INBOXVALID_API_URL=http://localhost:3001 npx tsx src/server.ts
```
---
### CLI — Direct curl (Mock API)
```bash
# Valid
curl -X POST http://localhost:3001/verify -H 'Content-Type: application/json' -d '{"email":"user@gmail.com"}'
# Invalid
curl -X POST http://localhost:3001/verify -H 'Content-Type: application/json' -d '{"email":"test@invalid-domain.test"}'
# Risky
curl -X POST http://localhost:3001/verify -H 'Content-Type: application/json' -d '{"email":"temp@example.com"}'
# Health check
curl http://localhost:3001/health
```
---
### Simulate Failure & Retry
```bash
# Trigger 2 failures on the mock API
curl -X POST http://localhost:3001/admin/fail -H 'Content-Type: application/json' -d '{"count":2}'
# Then verify — the client retries and eventually succeeds
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"verify_email","arguments":{"address":"user@gmail.com"}}}' | \
INBOXVALID_API_URL=http://localhost:3001 npx tsx src/server.ts
```
## 12. Design Decisions
### Why separate the MCP server from the InboxValid client?
The MCP server handles protocol concerns (tool registration, input/output formatting). The InboxValid client handles HTTP communication, retry logic, and response validation. This separation:
- Makes each layer independently testable
- Allows swapping the mock API for a real InboxValid service with zero changes to the MCP layer
- Keeps the retry/backoff logic encapsulated and reusable
### Why structured responses instead of throwing errors?
The `VerificationResult` type always has `address`, `status`, and `reason` fields, even for errors. This means:
- MCP clients always receive predictable JSON, never raw exceptions
- Error handling is explicit and type-safe
- The AI agent can reason about failures without parsing error messages
### Why Zod for validation?
Zod provides runtime type checking that complements TypeScript's compile-time checking. The MCP SDK also integrates natively with Zod schemas for tool input validation.
### Why a mock API instead of mocking fetch?
A real Express server running on a random port during tests exercises the full HTTP path, including serialization, headers, status codes, and network errors. This provides higher confidence than mocking `fetch`.
## License
MIT
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues