# Test MCP Server for MCP E2E Tests

A test MCP server used for E2E tests in the MCP Client. This originally was based off of the MCP Everything Server, but has been given new capabilities which the MCP Client handles.

## Building

```bash
npm install
npm run build
```

## Running

### STDIO Transport (Default)
```bash
npm start
# or

```node dist/index.js stdio

### SSE Transport
```bash
npm run start:sse
# or
node dist/index.js sse
```

### Streamable HTTP Transport
```bash
npm run start:streamableHttp
# or
node dist/index.js streamableHttp
```

### Streamable HTTP with OAuth2 Authentication
```bash
npm run start:streamableHttp:auth
# or
node dist/index.js streamableHttp --auth
```

This transport requires [OAuth2 Bearer token authentication](#oauth2-configuration) for all MCP endpoints.

## Available Tools

| Tool | Description |
|------|-------------|
| `echo` | Echoes back the input |
| `add` | Adds two numbers |
| `longRunningOperation` | Demonstrates progress updates |
| `printEnv` | Prints environment variables |
| `sampleLLM` | Samples from an LLM using MCP's sampling feature |
| `getTinyImage` | Returns a small test image |
| `annotatedMessage` | Demonstrates content annotations |
| `getResourceReference` | Returns a resource reference |
| `getResourceLinks` | Returns multiple resource links |
| `structuredContent` | Returns structured content with schema |
| `zip` | Creates a zip file from URLs |
| `listRoots` | Lists MCP roots (if client supports) |
| `startElicitation` | Demonstrates user input elicitation (if client supports) |

## Available Prompts

| Prompt | Description |
|--------|-------------|
| `simple_prompt` | A prompt without arguments |
| `complex_prompt` | A prompt with temperature and style arguments |
| `resource_prompt` | A prompt that includes an embedded resource |

## Resources

The server provides 100 static resources:
- Even-numbered resources contain plaintext
- Odd-numbered resources contain binary data
- Accessible via URI pattern: `test://static/resource/{id}`



## Project Structure

```
src/
├── index.ts              # Main entry point with transport selection
├── logger.ts             # Pino logger configuration
├── server/
│   └── index.ts          # Server creation and request handler wiring
├── tools/
│   ├── index.ts          # Tool registry, listing, and call routing
│   ├── types.ts          # Tool type definitions
│   ├── constants.ts      # Tool names enum and constants
│   └── definitions/      # Individual tool definitions
│       ├── echo.ts
│       ├── add.ts
│       └── ...           # One file per tool
├── prompts/
│   └── index.ts          # Prompt definitions and handlers
├── resources/
│   └── index.ts          # Resource definitions and handlers
├── oauth/
│   ├── index.ts          # OAuth module exports
│   ├── config.ts         # OAuth configuration
│   ├── types.ts          # OAuth type definitions
│   ├── stores.ts         # In-memory token/client stores
│   ├── helpers.ts        # Token generation utilities
│   ├── middleware.ts     # Auth middleware
│   └── routes.ts         # OAuth endpoints
└── transports/
    ├── stdio.ts              # Standard I/O transport
    ├── sse.ts                # Server-Sent Events transport
    └── streamableHttpServer.ts # Streamable HTTP (with optional OAuth2)
```

## Scripts

| Command | Description |
|---------|-------------|
| `npm run build` | Compile TypeScript to JavaScript |
| `npm run clean` | Remove `dist/` and build artifacts |
| `npm run clean:all` | Remove `dist/`, `node_modules/`, logs, and all generated files |
| `npm run lint` | Check for linting errors |
| `npm run lint:fix` | Fix auto-fixable linting errors |
| `npm run format` | Format code with Prettier |
| `npm run format:check` | Check if code is formatted |
| `npm run watch` | Watch mode for development |
| `npm start` | Start with STDIO transport |
| `npm run start:stdio` | Start with STDIO transport |
| `npm run start:sse` | Start with SSE transport |
| `npm run start:streamableHttp` | Start with Streamable HTTP transport |
| `npm run start:streamableHttp:auth` | Start with Streamable HTTP + OAuth2 |
| `npm test` | Run all tests |
| `npm run test:watch` | Run tests in watch mode |
| `npm run test:unit` | Run only unit tests |
| `npm run test:e2e` | Run only E2E tests |
| `npm run test:coverage` | Run tests with coverage report |

## Testing

This project uses [Vitest](https://vitest.dev/) for testing, with both unit and end-to-end (E2E) tests.

### Test Structure

```
src/
├── tools/
│   └── __tests__/           # Unit tests for tools
│       ├── echo.test.ts
│       ├── add.test.ts
│       ├── getTinyImage.test.ts
│       └── index.test.ts
├── prompts/
│   └── __tests__/           # Unit tests for prompts
│       └── index.test.ts
├── resources/
│   └── __tests__/           # Unit tests for resources
│       └── index.test.ts
└── oauth/
    └── __tests__/           # Unit tests for OAuth
        ├── helpers.test.ts
        └── middleware.test.ts

e2e/                         # End-to-end tests
├── helpers/
│   └── server.ts            # Test helpers for server management
├── stdio.test.ts            # STDIO transport tests
├── sse.test.ts              # SSE transport tests
├── streamableHttp.test.ts   # Streamable HTTP transport tests
└── oauth.test.ts            # OAuth2 flow tests
```

### Running Tests

```bash
# Run all tests
npm test

# Run tests in watch mode (during development)
npm run test:watch

# Run only unit tests
npm run test:unit

# Run only E2E tests
npm run test:e2e

# Run tests with coverage report
npm run test:coverage
```

### Unit Tests

Unit tests verify individual components in isolation:

- **Tools**: Test handler outputs for each tool (echo, add, getTinyImage, etc.)
- **Prompts**: Test prompt generation and completion handling
- **Resources**: Test resource creation, listing, pagination, and reading
- **OAuth**: Test token generation, validation, and middleware

Example test:

```typescript
import { describe, it, expect } from "vitest";
import { handler } from "../definitions/echo.js";

describe("echo tool", () => {
  it("should echo the message back", async () => {
    const result = await handler({ message: "hello" }, mockExtra, mockContext);
    expect(result.content[0].text).toBe("Echo: hello");
  });
});
```

### E2E Tests

E2E tests spin up the actual server and test real client-server communication:

- **STDIO Transport**: Tests using `StdioClientTransport`
- **SSE Transport**: Tests using `SSEClientTransport`
- **Streamable HTTP Transport**: Tests using `StreamableHTTPClientTransport`
- **OAuth Flow**: Tests full OAuth2 flows (client_credentials, authorization_code with PKCE, refresh_token)

E2E tests verify:
- Server startup and connection
- Tool listing and execution
- Prompt listing and retrieval
- Resource listing, pagination, and reading
- OAuth token issuance, introspection, and revocation
- Authenticated MCP requests

### Prerequisites for Testing

Before running tests, ensure the project is built:

```bash
npm run build
```

E2E tests require the built `dist/` files since they spawn server processes.

### Configuration

Configure logging via environment variables:

| Variable | Description | Default |
|----------|-------------|---------|
| `LOG_LEVEL` | Log level (trace, debug, info, warn, error, fatal) | `debug` |

### Component Loggers

The codebase uses child loggers for different components:
- `server` - Core server operations
- `transport` - Transport layer (stdio, sse, http)
- `tools` - Tool execution
- `oauth` - OAuth2 authentication


## OAuth2 Configuration

Configure via environment variables (optional, defaults provided for testing):
- `OAUTH_CLIENT_ID` - Client ID (default: `mcp-client`)
- `OAUTH_CLIENT_SECRET` - Client secret (default: `mcp-secret`)
- `OAUTH_TOKEN_SECRET` - Secret for signing tokens (change in production!)
- `PORT` - Server port (default: `3001`)

#### Supported Grant Types

| Grant Type | Description |
|------------|-------------|
| `client_credentials` | Server-to-server authentication |
| `authorization_code` | User authorization flow (with optional PKCE) |
| `refresh_token` | Refresh an expired access token |

#### Getting a Token (Client Credentials)

```bash
curl -X POST http://localhost:3001/oauth/token \
  -d "grant_type=client_credentials" \
  -d "client_id=mcp-client" \
  -d "client_secret=mcp-secret"
```

#### Authorization Code Flow (with PKCE)

1. **Generate PKCE values** (client-side):
```bash
# Generate code_verifier (43-128 chars)
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=' | tr '/+' '_-')

# Generate code_challenge (S256)
CODE_CHALLENGE=$(echo -n $CODE_VERIFIER | openssl sha256 -binary | base64 | tr -d '=' | tr '/+' '_-')
```

2. **Redirect user to authorization endpoint**:
```
http://localhost:3001/oauth/authorize?
  response_type=code&
  client_id=mcp-client&
  redirect_uri=http://localhost:3000/callback&
  scope=mcp:read%20mcp:write&
  state=random-state&
  code_challenge=$CODE_CHALLENGE&
  code_challenge_method=S256
```

3. **Exchange code for tokens**:
```bash
curl -X POST http://localhost:3001/oauth/token \
  -d "grant_type=authorization_code" \
  -d "code=<authorization-code>" \
  -d "redirect_uri=http://localhost:3000/callback" \
  -d "client_id=mcp-client" \
  -d "code_verifier=$CODE_VERIFIER"
```

#### Refreshing Tokens

```bash
curl -X POST http://localhost:3001/oauth/token \
  -d "grant_type=refresh_token" \
  -d "refresh_token=<your-refresh-token>" \
  -d "client_id=mcp-client"
```

Response (for authorization_code and refresh_token grants):
```json
{
  "access_token": "uuid-token.signature",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rt_uuid...",
  "scope": "mcp:read mcp:write"
}
```

#### Using the Token

Include the token in all MCP requests:
```bash
curl -X POST http://localhost:3001/mcp \
  -H "Authorization: Bearer <your-access-token>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}'
```

#### OAuth2 Endpoints

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/.well-known/oauth-authorization-server` | GET | Authorization Server Metadata ([RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)) |
| `/oauth/register` | POST | Dynamic Client Registration ([RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)) |
| `/oauth/authorize` | GET | Authorization endpoint for authorization_code flow |
| `/oauth/token` | POST | Token endpoint (all grant types) |
| `/oauth/introspect` | POST | Token introspection ([RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662)) |
| `/oauth/revoke` | POST | Token revocation ([RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009)) |
| `/` | GET | Server info and endpoint documentation |

#### Authorization Server Metadata

Clients can discover OAuth2 configuration automatically:

```bash
curl http://localhost:3001/.well-known/oauth-authorization-server
```

Response:
```json
{
  "issuer": "http://localhost:3001",
  "authorization_endpoint": "http://localhost:3001/oauth/authorize",
  "token_endpoint": "http://localhost:3001/oauth/token",
  "registration_endpoint": "http://localhost:3001/oauth/register",
  "introspection_endpoint": "http://localhost:3001/oauth/introspect",
  "revocation_endpoint": "http://localhost:3001/oauth/revoke",
  "grant_types_supported": ["client_credentials", "authorization_code", "refresh_token"],
  "token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic", "none"],
  "code_challenge_methods_supported": ["plain", "S256"],
  "scopes_supported": ["mcp:read", "mcp:write"]
}
```

#### Dynamic Client Registration

Register a new client dynamically:

```bash
curl -X POST http://localhost:3001/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My MCP Client",
    "grant_types": ["authorization_code", "refresh_token"],
    "redirect_uris": ["http://localhost:3000/callback"],
    "scope": "mcp:read mcp:write"
  }'
```

Response:
```json
{
  "client_id": "client-abc123...",
  "client_secret": "xyz789...",
  "client_id_issued_at": 1234567890,
  "client_secret_expires_at": 0,
  "client_name": "My MCP Client",
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "client_secret_post",
  "scope": "mcp:read mcp:write"
}
```

Use the returned `client_id` and `client_secret` to obtain access tokens.

## Adding New Capabilities

### Adding a New Tool

Each tool lives in its own file under `src/tools/definitions/`. To add a new tool:

1. **Create a new file** `src/tools/definitions/myNewTool.ts`:
   ```typescript
   import { z } from "zod";
   import { zodToJsonSchema } from "zod-to-json-schema";
   import type { ToolDefinition, ToolHandler, ToolContext } from "../types.js";

   // Tool name (exported for use in index.ts)
   export const name = "myNewTool";

   // Schema for input validation
   const inputSchema = z.object({
     param1: z.string().describe("Description of param1"),
   });

   // Tool definition with description and JSON schema
   export const definition: ToolDefinition = {
     description: "Description of what the tool does",
     inputSchema: zodToJsonSchema(inputSchema),
   };

   // Handler implementation
   export const handler: ToolHandler = async (args, extra, context) => {
     const { param1 } = inputSchema.parse(args);
     // Your implementation here
     return {
       content: [{ type: "text", text: `Result: ${param1}` }],
     };
   };
   ```

2. **Register it** in `src/tools/index.ts`:
   ```typescript
   import * as myNewTool from "./definitions/myNewTool.js";

   // Add to tools array in getTools()
   {
     name: myNewTool.name,
     description: myNewTool.definition.description,
     inputSchema: myNewTool.definition.inputSchema,
   }

   // Add to handleToolCall()
   if (name === myNewTool.name) {
     return myNewTool.handler(args, extra, context);
   }
   ```

### Adding a New Prompt

1. **Add the prompt name** to `src/prompts/index.ts`:
   ```typescript
   export enum PromptName {
     // ... existing prompts
     MY_PROMPT = "my_prompt",
   }
   ```

2. **Register it** in `getPrompts()` and implement in `getPrompt()`.

### Adding a New Resource

Modify `src/resources/index.ts` to add new resource types or extend the resource generation logic.
