A minimal MCP (Model Context Protocol) server prototype that validates architecture, testing strategy, authorization concept, and dev-tool support.
This prototype exposes a single tool: getCurrentUserProfile, which returns the profile of the current authenticated user based on an internal authorization context.
The prototype is split into three layers:
- Core/Domain Layer (
src/core/): Pure business logic (user data, lookup, auth helpers, mapping) - MCP Tool Layer (
src/tools/): MCP tool implementations - Transport/Runtime Layer (
src/): STDIO transport and server entry point
- Node.js 18+ (with ES modules support)
- npm or yarn
npm installThe server supports loading environment variables from a .env file. You can copy .env.example to .env and configure your settings:
cp .env.example .env
# Edit .env with your configurationThe npm start and npm run inspector commands automatically load variables from .env using dotenv-cli.
npm run buildThis compiles TypeScript to JavaScript in the dist/ directory.
Run all tests (unit + integration):
npm testRun tests in watch mode:
npm run test:watchThe server uses STDIO transport and supports two authorization modes. The server will fail to start if the auth configuration is invalid.
Valid configurations:
- Dev mode:
DEV_STATIC_AUTHORIZATION=trueANDDEV_STATIC_AUTHORIZATION_USER_IDmust be set - Production mode:
DEV_STATIC_AUTHORIZATIONis false/not set ANDJWT_JWKS_URLANDJWT_EXPECTED_AUDIENCEmust be set
Enable dev mode for development and testing:
Option 1: Using .env file (recommended)
Create a .env file in the project root:
DEV_STATIC_AUTHORIZATION=true
DEV_STATIC_AUTHORIZATION_USER_ID=user_1Then run:
npm start
# or
npm run inspectorOption 2: Using environment variables directly
# Enable dev mode and set user ID
DEV_STATIC_AUTHORIZATION=true DEV_STATIC_AUTHORIZATION_USER_ID=user_1 npm startOr after building:
DEV_STATIC_AUTHORIZATION=true DEV_STATIC_AUTHORIZATION_USER_ID=user_1 node dist/index.jsWhen DEV_STATIC_AUTHORIZATION is not set or set to false, the server uses JWT-based authorization. The JWT token must be passed by the caller as a tool argument:
Option 1: Using .env file (recommended)
Create a .env file in the project root:
DEV_STATIC_AUTHORIZATION=false
JWT_JWKS_URL=https://your-auth-provider.com/.well-known/jwks.json
JWT_EXPECTED_AUDIENCE=your-audience
JWT_USER_ID_FIELD=subThen run:
npm start
# or
npm run inspectorOption 2: Using environment variables directly
# Production mode - configure JWT settings
JWT_JWKS_URL=https://your-auth-provider.com/.well-known/jwks.json \
JWT_EXPECTED_AUDIENCE=your-audience \
npm startCalling the tool in production:
{
"name": "getCurrentUserProfile",
"arguments": {
"token": "your-jwt-token-here"
}
}Environment Variables:
Dev Mode:
DEV_STATIC_AUTHORIZATION: Set totrueor1to enable dev modeDEV_STATIC_AUTHORIZATION_USER_ID: User ID to use in dev mode (e.g.,user_1,user_2)
Production Mode (JWT Configuration):
JWT_JWKS_URL: JWKS endpoint URL for token signature verification (required)JWT_EXPECTED_AUDIENCE: Expected audience (aud) claim value (required)JWT_USER_ID_FIELD: Field name in JWT payload containing user ID (default:sub)
Note: The JWT token itself is passed by the caller as the token parameter in the tool arguments, not via environment variables.
-
Build the server:
npm run build
-
Install MCP Inspector (if not already installed):
npm install -g @modelcontextprotocol/inspector
-
Configure MCP Inspector to use this server: Start a mcp inspector instance which will run its own instance of the mcp server. STDIO will be used for communication.
- Server command:
npx @modelcontextprotocol/inspector node dist/index.js - Before you connect to the mcp server, set environment variables for dev mode:
DEV_STATIC_AUTHORIZATION=trueDEV_STATIC_AUTHORIZATION_USER_ID=user_1(oruser_2)
- Server command:
-
Inspect and test:
- Connect to mcp server
- List available tools (should show
getCurrentUserProfile) - Call
getCurrentUserProfilewith empty input - Verify the returned profile matches the current user
user_1: Alice Example (admin) - alice@example.comuser_2: Bob Example (user) - bob@example.com
DEV_STATIC_AUTHORIZATION=true DEV_STATIC_AUTHORIZATION_USER_ID=user_1 npm start
# Call getCurrentUserProfile → returns Alice's profileDEV_STATIC_AUTHORIZATION=true DEV_STATIC_AUTHORIZATION_USER_ID=unknown npm start
# Call getCurrentUserProfile → returns "User not found" error# Don't set DEV_STATIC_AUTHORIZATION (or set to false)
# Don't set JWT_TOKEN
npm start
# Call getCurrentUserProfile → returns "No authenticated user" error# Set JWT configuration
JWT_JWKS_URL=https://auth.example.com/.well-known/jwks.json \
JWT_EXPECTED_AUDIENCE=my-app \
JWT_USER_ID_FIELD=sub \
npm start
# Call getCurrentUserProfile with token argument:
# { "name": "getCurrentUserProfile", "arguments": { "token": "valid-jwt-token" } }
# → returns user profile from JWT token# Set JWT configuration
JWT_JWKS_URL=https://auth.example.com/.well-known/jwks.json \
JWT_EXPECTED_AUDIENCE=my-app \
npm start
# Call getCurrentUserProfile with invalid token:
# { "name": "getCurrentUserProfile", "arguments": { "token": "invalid-token" } }
# → returns "No authenticated user" error# Set JWT configuration
JWT_JWKS_URL=https://auth.example.com/.well-known/jwks.json \
JWT_EXPECTED_AUDIENCE=my-app \
npm start
# Call getCurrentUserProfile without token argument:
# { "name": "getCurrentUserProfile", "arguments": {} }
# → returns "No authenticated user" errorDEV_STATIC_AUTHORIZATION=true npm start
# DEV_STATIC_AUTHORIZATION_USER_ID not set
# Call getCurrentUserProfile → returns "No authenticated user" errorsrc/
├── core/ # Core/domain layer (pure logic)
│ ├── types.ts
│ ├── data.ts # Fake user data
│ ├── user-lookup.ts
│ ├── user-lookup.test.ts # Unit tests
│ ├── auth.ts
│ ├── auth.test.ts # Unit tests
│ ├── profile-mapper.ts
│ └── profile-mapper.test.ts # Unit tests
├── tools/ # MCP tool layer
│ ├── getCurrentUserProfile.ts
│ └── getCurrentUserProfile.test.ts # Unit tests
├── runtime/ # Runtime context
│ └── context.ts
└── index.ts # Entry point (stdio transport)
tests/
└── integration/ # Integration tests
└── getCurrentUserProfile.integration.test.ts
-
Unit Tests (
src/**/*.test.ts): Test individual functions in isolation- Core layer:
auth.test.ts,user-lookup.test.ts,profile-mapper.test.ts - Tool layer:
getCurrentUserProfile.test.ts(tests tool logic and decision flow)
- Core layer:
-
Integration Tests (
tests/integration/*.integration.test.ts): Test multiple components working togethergetCurrentUserProfile.integration.test.ts(tests auth + lookup + mapping integration)
✅ Each function in the core/domain layer can be unit-tested independently
✅ The getCurrentUserProfile tool has reliable integration tests covering success and error paths
✅ Authorization concept (currentUserId → profile) is clearly expressed and tested
✅ Running server can be inspected and exercised via MCP Inspector
✅ Architecture can be extended later (Firebase, WebSocket, more tools) without redesign