Skip to main content
Glama
05tanish

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

![MCP Inspector — Server Connected](assets/ServerConnected.png)

#### Valid Email Result

![MCP Inspector — Valid Email](assets/ValidEmail.png)

#### Invalid Email Result

![MCP Inspector — Invalid Email](assets/InvalidEmail.png)

---

### 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