Skip to main content
Glama
cameronsjo

MCP Server Template

by cameronsjo
README.md
# MCP Server Template

A production-ready template for building Model Context Protocol (MCP) servers with TypeScript.

## Features

- **MCP SDK 1.24.3** - Latest SDK with 2025-11-25 spec support
- **Dual Transport** - Stdio (Claude Desktop) and HTTP (cloud deployment)
- **OAuth 2.1 Foundations** - Protected resource metadata, bearer token structure
- **SQLite Caching** - TTL-based caching with sql.js (WebAssembly)
- **Observability** - Sentry error tracking + OpenTelemetry tracing
- **Security** - PII sanitization, rate limiting, DNS rebinding protection
- **Type Safety** - Strict TypeScript with Zod validation

## Quick Start

```bash
# Clone and install
git clone <this-repo> my-mcp-server
cd my-mcp-server
npm install

# Development mode (hot reload)
npm run dev

# Build and run
npm run build
npm start

# Test with MCP Inspector
npm run inspector
```

## Project Structure

```
src/
├── index.ts              # CLI entry point
├── server.ts             # MCP server implementation
├── instrumentation.ts    # Sentry + OpenTelemetry setup
├── config/
│   └── index.ts          # Environment configuration
├── db/
│   ├── database.ts       # SQLite connection (sql.js)
│   └── cache.ts          # TTL-based caching
├── tools/
│   ├── registry.ts       # Tool registration pattern
│   └── examples.ts       # Example tool implementations
├── transport/
│   └── http-transport.ts # HTTP with OAuth foundations
├── shared/
│   ├── logger.ts         # Structured logging
│   ├── errors.ts         # Custom error classes
│   ├── rate-limiter.ts   # Per-source rate limiting
│   ├── pii-sanitizer.ts  # PII detection/removal
│   └── tracing.ts        # OpenTelemetry utilities
└── types/
    └── index.ts          # TypeScript type definitions
```

## Configuration

Environment variables (prefix with `MCP_SERVER_`):

| Variable | Default | Description |
|----------|---------|-------------|
| `MCP_SERVER_NAME` | `mcp-server-template` | Server name |
| `MCP_SERVER_VERSION` | `0.1.0` | Server version |
| `MCP_SERVER_LOG_LEVEL` | `info` | Log level: debug, info, warning, error |
| `MCP_SERVER_DB_PATH` | `.data/cache.db` | SQLite database path |
| `MCP_SERVER_CACHE_ENABLED` | `true` | Enable/disable caching |
| `MCP_SERVER_CACHE_TTL` | `3600` | Default cache TTL (seconds) |
| `MCP_SERVER_TIMEOUT` | `30000` | Request timeout (ms) |
| `MCP_SERVER_TRANSPORT` | `stdio` | Transport: `stdio` or `http` |
| `MCP_SERVER_PORT` | `3000` | HTTP port (when transport=http) |
| `MCP_SERVER_HOST` | `127.0.0.1` | HTTP host (when transport=http) |
| `MCP_SERVER_SENTRY_DSN` | - | Sentry DSN for error tracking |
| `OTEL_ENABLED` | `false` | Enable OpenTelemetry tracing |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | - | OTLP collector endpoint |
| `MCP_SERVER_DEBUG` | `false` | Debug mode (skips auth) |

## Creating Tools

Tools are registered with the ToolRegistry using Zod schemas:

```typescript
import { z } from 'zod';
import { getToolRegistry } from './tools/registry.js';

// Define input schema
const MyToolInputSchema = z.object({
  query: z.string().min(1).describe('Search query'),
  limit: z.number().positive().optional().default(10),
});

type MyToolInput = z.infer<typeof MyToolInputSchema>;

// Implement handler
async function myToolHandler(input: MyToolInput) {
  // Your implementation here
  return {
    success: true,
    data: { results: [] },
  };
}

// Register tool
const registry = getToolRegistry();
registry.register(
  'my_tool',
  'Description of what this tool does',
  MyToolInputSchema,
  myToolHandler
);
```

## Transport Modes

### Stdio (Default)

For Claude Desktop and local integrations:

```bash
npm start
```

Add to Claude Desktop config:

```json
{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["/path/to/dist/index.js"]
    }
  }
}
```

### HTTP

For cloud deployment:

```bash
MCP_SERVER_TRANSPORT=http npm start
```

Endpoints:
- `GET /health` - Health check
- `GET /.well-known/mcp` - MCP server metadata
- `GET /.well-known/oauth-protected-resource` - OAuth metadata (RFC 9728)
- `GET /mcp` - SSE stream for server events
- `POST /mcp` - JSON-RPC requests
- `DELETE /mcp` - Close session

## Security Features

### PII Sanitization

Automatically detects and masks sensitive data:

```typescript
import { sanitizePii } from './shared/pii-sanitizer.js';

const safe = sanitizePii('Contact: user@example.com');
// Output: "Contact: [EMAIL]"
```

### Rate Limiting

Per-source rate limiting with exponential backoff:

```typescript
import { getRateLimiter } from './shared/rate-limiter.js';

const limiter = getRateLimiter();
limiter.configure('external-api', {
  requestsPerWindow: 100,
  windowMs: 60000,
});

await limiter.waitForSlot('external-api');
// Make request...
```

### DNS Rebinding Protection

HTTP transport validates Host headers against allowlist.

## Observability

### Sentry

Error tracking with PII filtering:

```bash
MCP_SERVER_SENTRY_DSN=https://xxx@sentry.io/xxx npm start
```

### OpenTelemetry

Distributed tracing:

```bash
OTEL_ENABLED=true \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
npm start
```

Use tracing utilities:

```typescript
import { withSpan, createApiSpan } from './shared/tracing.js';

const result = await withSpan('my-operation', async (span) => {
  span.setAttribute('custom.attr', 'value');
  return doWork();
});
```

## Development

```bash
# Type check
npm run check

# Lint
npm run lint
npm run lint:fix

# Format
npm run format

# Test
npm test
npm run test:coverage

# Validate (all checks)
npm run validate
```

## MCP 2025-11-25 Spec Compliance

| Feature | Status |
|---------|--------|
| Tools | ✅ Implemented |
| Resources | 📝 Scaffolded |
| Prompts | 📝 Scaffolded |
| Streamable HTTP | ✅ Implemented |
| .well-known/mcp | ✅ Implemented |
| OAuth 2.1 Foundations | ✅ Scaffolded |
| Tasks | ❌ Not yet |
| Elicitation | ❌ Not yet |

## OAuth 2.1 Implementation

The template includes foundations for OAuth 2.1 per the MCP spec:

1. **Protected Resource Metadata** (RFC 9728) at `/.well-known/oauth-protected-resource`
2. **Bearer token middleware** structure (implement JWT validation)
3. **WWW-Authenticate** headers with resource_metadata reference
4. **Scope checking** structure for tool authorization

To complete OAuth integration:

1. Choose an authorization server (Auth0, Logto, etc.)
2. Implement JWT validation in `bearerAuthMiddleware`
3. Add JWKS fetching and caching
4. Configure scopes per tool

See [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) for details.

## License

MIT

## References

- [MCP Specification](https://modelcontextprotocol.io/specification/2025-11-25)
- [MCP SDK](https://www.npmjs.com/package/@modelcontextprotocol/sdk)
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)

TDQS

A3.5/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: add performs arithmetic, cache handles key-value storage, echo returns input with optional transformation, and status provides server metadata. There is no overlap or ambiguity between these functions.

Naming Consistency5/5

All tool names follow a consistent, simple verb-based pattern (add, cache, echo, status) without mixing conventions like snake_case or camelCase. This uniformity makes the set predictable and easy to understand.

Tool Count4/5

With 4 tools, the count is reasonable for a template server, covering basic utilities like arithmetic, caching, echoing, and status checks. It's slightly minimal but well-scoped for demonstration purposes, lacking only minor expansions.

Completeness3/5

As a template server, it covers fundamental operations but has notable gaps for a more robust utility set, such as missing tools for deletion, advanced caching operations, or error handling. However, it provides a complete basic workflow for its intended scope.