A Model Context Protocol (MCP) server that exposes tools to query external API endpoints.
This MCP server provides a standardized interface to interact with external APIs through the MCP protocol. It supports both stdio (for local spawned processes) and HTTP transports (for remote access).
- MCP Protocol Compliant: Full support for MCP tool invocation
- Dual Transport: Both stdio and HTTP transports supported
- Type-Safe: Built with TypeScript in strict mode
- Simple Configuration: Environment variable-based configuration
- Error Handling: Clear, actionable error messages
- Node.js v22.x or later
- pnpm package manager
# Clone the repository
git clone <repository-url>
cd hackathon
# Install dependencies
pnpm install
# Copy environment configuration
cp .env.example .env
# Build the project
pnpm buildpnpm start:stdiopnpm start:httppnpm startsrc/
├── features/
│ └── mcp-api-server/ # MCP API server feature
│ ├── transports/ # Transport implementations
│ ├── tools/ # MCP tools
│ ├── services/ # Business logic
│ ├── constants.ts # Constants and configuration
│ ├── types.ts # TypeScript interfaces
│ └── server.ts # MCP server setup
├── shared/ # Shared utilities
└── index.ts # Main entry point
Configuration is done via environment variables. Copy .env.example to .env and adjust values as needed:
cp .env.example .env| Variable | Default | Description |
|---|---|---|
API_BASE_URL |
http://10.138.80.113:5000 |
Base URL of the external API |
API_TIMEOUT |
5000 |
API request timeout in milliseconds |
TRANSPORT |
auto |
Transport type: stdio, http, or auto |
HTTP_PORT |
3000 |
Port for HTTP transport (only used when http) |
DEBUG |
false |
Enable debug logging (true or false) |
When TRANSPORT=auto (default), the server automatically detects the appropriate transport:
- TTY detected: Uses HTTP transport (port 3000)
- No TTY (piped/spawned): Uses stdio transport
Register a new user account.
Parameters:
email(string, required): User email addresspassword(string, required): User password
Example:
{
"method": "tools/call",
"params": {
"name": "signup",
"arguments": {
"email": "user@example.com",
"password": "SecurePass123!"
}
}
}Authenticate an existing user.
Parameters:
email(string, required): User email addresspassword(string, required): User password
Example:
{
"method": "tools/call",
"params": {
"name": "login",
"arguments": {
"email": "user@example.com",
"password": "SecurePass123!"
}
}
}Retrieve the currently stored authentication token.
Parameters: None
Example:
{
"method": "tools/call",
"params": {
"name": "get-token",
"arguments": {}
}
}Complete set of tools for managing organizations with full Create, Read, Update, Delete operations.
Retrieve a list of all organizations the authenticated user has permission to view.
Parameters: None (future support for pagination/filters)
Authentication: Required (JWT token)
Example:
{
"method": "tools/call",
"params": {
"name": "get-all-organizations",
"arguments": {}
}
}Success Response:
{
"success": true,
"data": [
{
"id": "org-123",
"name": "Acme Corp",
"description": "Leading technology company",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
]
}Create a new organization with the specified name and optional description.
Parameters:
name(string, required): Organization name (1-255 characters)description(string, optional): Organization description (max 1000 characters)
Authentication: Required (JWT token)
Example:
{
"method": "tools/call",
"params": {
"name": "create-organization",
"arguments": {
"name": "Tech Innovators Inc",
"description": "Cutting-edge technology solutions"
}
}
}Success Response:
{
"success": true,
"data": {
"id": "org-456",
"name": "Tech Innovators Inc",
"description": "Cutting-edge technology solutions",
"createdAt": "2024-01-20T14:25:00Z",
"updatedAt": "2024-01-20T14:25:00Z"
}
}Retrieve a specific organization by its unique identifier.
Parameters:
id(string, required): Organization ID
Authentication: Required (JWT token)
Example:
{
"method": "tools/call",
"params": {
"name": "get-organization",
"arguments": {
"id": "org-123"
}
}
}Success Response:
{
"success": true,
"data": {
"id": "org-123",
"name": "Acme Corp",
"description": "Leading technology company",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
}Error Response (Not Found):
{
"success": false,
"error": "Organization not found"
}Update an existing organization with full body replacement (PUT semantics).
Parameters:
id(string, required): Organization IDname(string, required): Organization name (1-255 characters)description(string, optional): Organization description (max 1000 characters)
Authentication: Required (JWT token)
Note: This is a complete replacement operation - all fields must be provided.
Example:
{
"method": "tools/call",
"params": {
"name": "update-organization",
"arguments": {
"id": "org-123",
"name": "Acme Corporation",
"description": "Global leader in technology solutions"
}
}
}Success Response:
{
"success": true,
"data": {
"id": "org-123",
"name": "Acme Corporation",
"description": "Global leader in technology solutions",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-20T16:45:00Z"
}
}Permanently delete an organization by its unique identifier.
Parameters:
id(string, required): Organization ID
Authentication: Required (JWT token)
Warning: This operation is irreversible. Organizations with dependencies cannot be deleted.
Example:
{
"method": "tools/call",
"params": {
"name": "delete-organization",
"arguments": {
"id": "org-789"
}
}
}Success Response:
{
"success": true,
"message": "Organization with ID 'org-789' has been successfully deleted."
}Error Response (Has Dependencies):
{
"success": false,
"error": "Cannot delete organization with existing dependencies"
}Queries the /api/hello endpoint and returns the API response.
Parameters: None
Example Request (MCP Inspector or client):
{
"method": "tools/call",
"params": {
"name": "hello",
"arguments": {}
}
}Example Response:
{
"content": [
{
"type": "text",
"text": "{\n \"message\": \"Hello from API\"\n}"
}
]
}Install the MCP Inspector:
npm install -g @modelcontextprotocol/inspectorTest the server with stdio transport:
pnpm build
mcp-inspector node dist/stdio-entry.jsStart the HTTP server:
pnpm start:httpTest with curl:
# Health check
curl http://localhost:3000/health
# Call the hello tool
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "hello",
"arguments": {}
}
}'The server provides clear error messages for common scenarios:
TIMEOUT: API request exceeded timeout limitNETWORK_ERROR: Cannot connect to API or DNS resolution failedAPI_ERROR: API returned an error status (4xx/5xx)INTERNAL_ERROR: Unexpected server error
Example error response:
{
"content": [
{
"type": "text",
"text": "Error: Request timed out after 5000ms. The API did not respond in time."
}
],
"isError": true
}If you see NETWORK_ERROR:
- Verify the API is running and accessible
- Check
API_BASE_URLin your.envfile - Ensure network connectivity to the API server
If you see TIMEOUT errors:
- Check if the API is responding slowly
- Increase
API_TIMEOUTin.env(default: 5000ms) - Verify the API endpoint is correct
# Clean build
rm -rf dist/
pnpm buildBuilt following the project constitution:
- ✅ YAGNI - Build only what's needed
- ✅ Feature-Based Architecture - Organized by features
- ✅ TypeScript + Node.js + pnpm - Modern stack
- ✅ SOLID Principles - Clean architecture
- ✅ Constants Pattern - Centralized configuration
- ✅ KISS - Keep it simple
- Create tool implementation in
src/features/mcp-api-server/tools/ - Define tool metadata (name, description, schema)
- Register tool in
src/features/mcp-api-server/server.ts - Rebuild:
pnpm build
- Node.js v22.x LTS installed on target server
- pnpm package manager
- Network access to external API endpoints
- Environment variables configured
-
Build the project:
pnpm install pnpm build
-
Configure environment:
# Create production .env file cp .env.example .env # Edit .env with production values nano .env
-
Run with process manager (recommended):
Using PM2:
npm install -g pm2 # For stdio transport pm2 start dist/stdio-entry.js --name mcp-api-server-stdio # For HTTP transport pm2 start dist/http-entry.js --name mcp-api-server-http --env production # Save configuration pm2 save pm2 startup
-
Verify deployment:
# Check logs pm2 logs mcp-api-server-http # Check status pm2 status # Test health endpoint (HTTP transport) curl http://localhost:3000/health
Create Dockerfile:
FROM node:22-alpine
WORKDIR /app
# Install pnpm
RUN npm install -g pnpm
# Copy package files
COPY package.json pnpm-lock.yaml ./
# Install dependencies
RUN pnpm install --frozen-lockfile --prod
# Copy source and build
COPY . .
RUN pnpm build
# Expose HTTP port
EXPOSE 3000
# Run HTTP transport by default
CMD ["node", "dist/http-entry.js"]Build and run:
docker build -t mcp-api-server .
docker run -d \
--name mcp-api-server \
-p 3000:3000 \
-e API_BASE_URL=http://10.138.80.113:5000 \
-e TRANSPORT=http \
mcp-api-serverMonitor the application in production:
# With PM2
pm2 monit
# View logs
pm2 logs --lines 100
# Restart if needed
pm2 restart mcp-api-server-httpFor HTTP transport, use the /health endpoint:
curl http://localhost:3000/healthExpected response:
{
"status": "healthy",
"transport": "http"
}- Environment Variables: Never commit
.envfiles to version control - Network Security: Restrict API access to trusted networks
- Rate Limiting: Consider adding rate limiting for HTTP transport
- HTTPS: Use a reverse proxy (nginx, caddy) for HTTPS in production
Example nginx configuration:
server {
listen 443 ssl;
server_name mcp-api.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}ISC