EssTech
Published on

Simplify MCP

Authors
  • avatar
    Name
    Heefan
    Twitter

After reading the introduction to MCP, some people say MCP is as simple as the USB Protocol. But this left me confused. This article is my attempt to clarify these confusions.

Starting from a REST booking system

Suppose we use a Next.js BFF Web App to build a Booking System. This is very straightforward: the web app directly calls a remote booking REST API.

How do we turn this pattern into the MCP model?

MCP Key Concepts:

Host (Application) – The app that initiates the request, here it’s the Next.js BFF. The Host is the control center of the MCP architecture, responsible for:

  • Deciding when to call external tools/services
  • Managing user interaction and business logic
  • Orchestrating calls to multiple MCP servers
  • Handling the final response and rendering

Client (Protocol Handler) – The implementation layer of the MCP protocol, acting as a bridge between Host and Server. The Client is responsible for:

  • Implementing the MCP protocol (JSON-RPC 2.0)
  • Managing connections to servers (stdio, SSE, WebSocket, etc.)
  • Handling protocol-level errors and retries
  • Serializing/deserializing requests and responses

Server (MCP Server) – The service provider, here it’s the Booking Server. The Server is responsible for:

  • Implementing business tools (search_flights, search_hotels, etc.)
  • Integrating with external APIs (flight, hotel, etc.)
  • Returning standardized MCP response format
  • Handling authentication, authorization, and data validation

Simple Analogy:

  • Host = Your phone (initiates requests)
  • Client = USB driver (protocol translation)
  • Server = USB drive (provides functionality)

After switching to the MCP model, it may seem unnecessarily complicated for a simple problem.

But when the requirements expand—users want to get booking info via chat, check the weather, find hotel deals, and car rental offers—things change. Now you need context.

Multi-Service MCP Architecture

Suppose a user asks via chat: "I want to fly from Singapore to Harstad, please check flights, local weather, hotel deals, and car rental from 8 Aug to 20 Aug." This is where MCP shines:

This architecture shows the real value of MCP:

  • Unified protocol: All services communicate via the same MCP protocol
  • Parallel processing: Multiple services can be called simultaneously
  • Context retention: The AI Agent can synthesize all information for a smart answer
  • Extensibility: Easily add new travel services (visa, insurance, etc.)

The Question: Why use MCP when you can just use an Agent?

In enterprise development, many services are proprietary and access-controlled. You might prefer to have an AI Agent call various APIs directly—so why introduce MCP as a middle layer?

1. Standardization & Interoperability

// Traditional Agent approach – each API has a different interface
const flightData = await fetch('/api/flights', {
  method: 'POST',
  body: JSON.stringify({ from: 'SIN', to: 'EVE' }),
})

const weatherData = await axios.get('/weather/api', {
  params: { city: 'Harstad', format: 'json' },
})

const hotelData = await request.post('/booking/hotels', {
  location: 'Harstad',
  checkin: '2024-08-08',
})
// MCP approach – unified protocol interface
const flightResult = await mcpClient.callTool('flight-server', 'search_flights', {
  from: 'SIN',
  to: 'EVE',
})

const weatherResult = await mcpClient.callTool('weather-server', 'get_weather', {
  location: 'Harstad',
})

const hotelResult = await mcpClient.callTool('hotel-server', 'search_hotels', {
  location: 'Harstad',
  date: '2024-08-08',
})

2. Development Efficiency & Maintenance

Problems with the traditional Agent approach:

  • Each API needs separate error handling
  • Different authentication mechanisms
  • Data format conversions
  • API changes require code changes in multiple places

MCP advantages:

  • Unified error handling
  • Standardized authentication
  • Consistent data formats
  • Plugin architecture, easy to extend

3. Security & Authorization

4. Ecosystem Effect

Imagine if every AI app had to:

  • Write custom integration code for every service
  • Handle all sorts of different API formats
  • Maintain multiple authentication systems

It’s like before USB, when every device needed its own driver and interface. MCP is the "USB protocol" for AI tools.

5. Real Business Value

# Traditional approach: adding a new service
- Study API docs (2-3 days)
- Write integration code (3-5 days)
- Test and debug (2-3 days)
- Total: 7-11 days

# MCP approach: adding a new service
- Implement MCP Server (1-2 days)
- Register with MCP ecosystem (minutes)
- Total: 1-2 days

6. Real-World Scenario Comparison

Suppose you want to add 100 new tools to ChatGPT:

Traditional approach:

  • OpenAI must review and integrate each tool separately
  • Developers must learn OpenAI’s specific API format
  • Every tool update requires re-review

MCP approach:

  • Any tool conforming to the MCP standard is plug-and-play
  • Developers only need to implement the MCP interface once
  • Tool updates are transparent to the host app

So the value of MCP is not technical irreplaceability, but:

  • Lower integration cost
  • Higher development efficiency
  • Ecosystem standards
  • Tool reusability

Just as we don’t ask "Why use HTTP, why not just TCP?", MCP provides a higher level of abstraction and standardization.

MCP Security and Authorization in Detail

Earlier we mentioned MCP provides a unified security layer, but how is this actually implemented?

1. Transport Layer Security

MCP supports multiple secure transport methods:

2. Authorization and Permission Control

MCP provides multi-layer security at the protocol level:

// MCP Server config example
interface MCPServerConfig {
  name: string
  version: string

  authorization: {
    type: 'bearer' | 'apikey' | 'oauth2' | 'custom'
    apiKey?: {
      header: string
      required: boolean
    }
    oauth2?: {
      authUrl: string
      tokenUrl: string
      scopes: string[]
    }
    customAuth?: (request: MCPRequest) => Promise<boolean>
  }

  permissions: {
    tools: {
      [toolName: string]: {
        allowedHosts: string[]
        rateLimits: {
          requests: number
          timeWindow: number
        }
        requireAuth: boolean
      }
    }
    resources: {
      read: boolean
      write: boolean
      delete: boolean
    }
  }
}

3. End-to-End Security Flow

Who Provides the Auth Provider?

This is a key question in MCP architecture. The Auth Provider can be provided by different parties, depending on the deployment scenario:

Implementation Scenarios:

1. Enterprise Internal

// Enterprise Auth Provider implementation
class EnterpriseAuthProvider implements AuthProvider {
  constructor(private ldapConfig: LDAPConfig) {}

  async validateCredentials(hostId: string, credentials: Credentials): Promise<AuthResult> {
    // 1. Connect to LDAP/AD
    const ldapClient = new LDAPClient(this.ldapConfig)

    // 2. Authenticate host app
    const appIdentity = await ldapClient.authenticate(credentials.clientId, credentials.secret)

    // 3. Get app permissions
    const permissions = await this.getAppPermissions(hostId)

    return {
      valid: true,
      hostId,
      permissions,
      token: this.generateJWT(appIdentity, permissions),
    }
  }
}

1.1 Microsoft EntraID (Azure AD) Integration

// Microsoft EntraID Auth Provider implementation
class EntraIDAuthProvider implements AuthProvider {
  constructor(private entraConfig: EntraIDConfig) {}

  async validateCredentials(hostId: string, credentials: Credentials): Promise<AuthResult> {
    try {
      // 1. Use Client Credentials Flow to authenticate app
      const tokenResponse = await this.getAccessToken(credentials)
      // 2. Validate token and get app info
      const appInfo = await this.validateToken(tokenResponse.access_token)
      // 3. Get app role assignments and permissions
      const permissions = await this.getApplicationPermissions(appInfo.appId, hostId)
      return {
        valid: true,
        hostId,
        permissions,
        token: tokenResponse.access_token,
        expiresAt: new Date(Date.now() + tokenResponse.expires_in * 1000),
      }
    } catch (error) {
      return { valid: false, error: `EntraID authentication failed: ${error.message}` }
    }
  }
  // ...implementation details omitted for brevity...
}

EntraID Config Example:

# MCP Server config - EntraID integration
mcp_server:
  name: 'travel-booking-server'
  version: '1.0.0'
  auth_provider:
    type: 'entraid'
    entraid:
      tenant_id: '${AZURE_TENANT_ID}'
      client_id: '${MCP_SERVER_CLIENT_ID}'
      client_secret: '${MCP_SERVER_CLIENT_SECRET}'
      scopes:
        - 'https://graph.microsoft.com/Application.Read.All'
        - 'api://mcp-travel-server/MCP.Access'
      role_permissions:
        'MCP.FlightSearch.Reader':
          permissions: ['search_flights', 'get_prices']
          rate_limits:
            requests_per_minute: 100
        'MCP.TravelAgent.Full':
          permissions: ['search_flights', 'search_hotels', 'make_reservation']
          rate_limits:
            requests_per_minute: 200
        'MCP.Admin':
          permissions: ['*']
          rate_limits:
            requests_per_minute: 500
  allowed_hosts:
    'claude.ai':
      entraid_app_id: '${CLAUDE_APP_ID}'
      required_roles: ['MCP.TravelAgent.Full']
    'openai.com':
      entraid_app_id: '${OPENAI_APP_ID}'
      required_roles: ['MCP.FlightSearch.Reader']

EntraID App Registration Example:

{
  "displayName": "MCP Travel Booking Server",
  "signInAudience": "AzureADMyOrg",
  "api": {
    "requestedAccessTokenVersion": 2,
    "oauth2PermissionScopes": [{ "id": "...", "value": "MCP.Access" }]
  },
  "appRoles": [{ "value": "MCP.FlightSearch.Reader" }, { "value": "MCP.TravelAgent.Full" }]
}

Enhanced Security Flow – EntraID Integration:

EntraID Integration Advantages:

  1. Enterprise-grade security: Leverage Microsoft’s identity management
  2. Fine-grained permissions: App Roles for precise access control
  3. Audit compliance: Integrate with Azure Monitor for full audit
  4. SSO integration: Seamless with enterprise SSO
  5. Conditional access: Device/location-based policies
  6. Multi-tenant support: B2B collaboration

2. Third-Party Service

// Auth0 integration example
class Auth0Provider implements AuthProvider {
  constructor(private auth0Config: Auth0Config) {}

  async validateCredentials(hostId: string, credentials: Credentials): Promise<AuthResult> {
    // 1. Call Auth0 API to validate
    const auth0Response = await fetch(`${this.auth0Config.domain}/oauth/token`, {
      /* ... */
    })
    const tokenData = await auth0Response.json()
    // 2. Parse permission scopes
    const permissions = this.parseScopes(tokenData.scope)
    return {
      valid: true,
      hostId,
      permissions,
      token: tokenData.access_token,
    }
  }
}

3. MCP Server Built-in

// MCP Server built-in authentication
class MCPServerAuthProvider implements AuthProvider {
  constructor(private apiKeyStore: APIKeyStore) {}

  async validateCredentials(hostId: string, credentials: Credentials): Promise<AuthResult> {
    // 1. Validate API Key
    const apiKeyInfo = await this.apiKeyStore.validate(credentials.apiKey)
    if (!apiKeyInfo.valid) {
      return { valid: false, error: 'Invalid API key' }
    }
    // 2. Check host whitelist
    if (!apiKeyInfo.allowedHosts.includes(hostId)) {
      return { valid: false, error: 'Host not authorized' }
    }
    // 3. Return permission info
    return {
      valid: true,
      hostId,
      permissions: apiKeyInfo.permissions,
      token: this.generateSessionToken(hostId, apiKeyInfo),
    }
  }
}

Deployment Config Example:

# MCP Server config
mcp_server:
  name: 'travel-booking-server'
  version: '1.0.0'
  auth_provider:
    type: 'enterprise' # options: enterprise, auth0, okta, builtin, aws_cognito
    enterprise:
    auth0:
    builtin:
  permissions:
    default_permissions: ['search_flights']
    host_specific:

Summary: Choosing an Auth Provider

ScenarioAuth ProviderAdvantagesWhen to Use
EnterpriseLDAP/AD/SSOIntegrates with existing systemsLarge internal apps
SaaSAuth0/OktaProfessional identity serviceNeed enterprise auth
Simple DeployBuilt-in API KeyQuick setupSmall team or prototype
Cloud NativeAWS Cognito/Azure ADDeep cloud integrationCloud-based projects
Open SourceJWT + Self-managedFull controlOpen source or self-host

So the Auth Provider is not provided by the MCP protocol itself, but is chosen and configured by the organization deploying the MCP Server, according to their security requirements and infrastructure.

4. Security Implementation Details

4.1 Token Management

// Secure MCP Client implementation
class SecureMCPClient {
  private authTokens: Map<string, AuthToken> = new Map()

  async connectToServer(serverConfig: MCPServerConfig) {
    // 1. Validate server certificate
    const serverInfo = await this.validateServerCertificate(serverConfig)
    // 2. Get auth token
    const authToken = await this.authenticate(serverConfig.authorization)
    // 3. Establish secure connection
    const connection = await this.establishSecureConnection(serverConfig, authToken)
    return connection
  }

  private async authenticate(authConfig: AuthConfig): Promise<AuthToken> {
    switch (authConfig.type) {
      case 'oauth2':
        return await this.handleOAuth2Flow(authConfig.oauth2)
      case 'apikey':
        return await this.handleAPIKeyAuth(authConfig.apiKey)
      case 'bearer':
        return await this.handleBearerTokenAuth(authConfig.bearer)
      default:
        throw new Error('Unsupported auth type')
    }
  }
}

4.2 Permission Validation & Rate Limiting

// MCP Server security middleware
class MCPSecurityMiddleware {
  async validateRequest(request: MCPRequest, context: SecurityContext): Promise<boolean> {
    // 1. Validate host identity
    const hostValid = await this.validateHost(context.hostId, context.credentials)
    if (!hostValid) return false
    // 2. Check tool permissions
    const toolPermissions = this.getToolPermissions(request.method, context.hostId)
    if (!toolPermissions.allowed) return false
    // 3. Rate limit check
    const rateLimitOk = await this.checkRateLimit(context.hostId, request.method)
    if (!rateLimitOk) return false
    // 4. Parameter validation
    const paramsValid = await this.validateParameters(request.params)
    if (!paramsValid) return false
    return true
  }

  private async checkRateLimit(hostId: string, method: string): Promise<boolean> {
    const key = `${hostId}:${method}`
    const current = (await this.redis.get(key)) || 0
    const limit = this.rateLimits[method] || 100
    if (current >= limit) {
      await this.logSecurityEvent('rate_limit_exceeded', { hostId, method })
      return false
    }
    await this.redis.incr(key)
    await this.redis.expire(key, 60) // 1 minute window
    return true
  }
}

5. Data Privacy & Auditing

// Security audit system
class MCPAuditLogger {
  async logSecurityEvent(event: SecurityEvent) {
    const auditRecord = {
      timestamp: new Date().toISOString(),
      hostId: event.hostId,
      serverId: event.serverId,
      action: event.action,
      success: event.success,
      ipAddress: event.ipAddress,
      userAgent: event.userAgent,
      requestData: this.sanitizeData(event.requestData),
      responseData: this.sanitizeData(event.responseData),
      authMethod: event.authMethod,
      permissions: event.permissions,
      rateLimitStatus: event.rateLimitStatus,
    }
    await this.writeToAuditLog(auditRecord)
    if (this.detectSuspiciousActivity(auditRecord)) {
      await this.triggerSecurityAlert(auditRecord)
    }
  }
  private sanitizeData(data: any): any {
    // Remove sensitive info: passwords, API keys, PII, etc.
    return this.deepClone(data, this.sensitiveFieldsFilter)
  }
}

6. Security Advantages over Traditional APIs

Security AspectTraditional APIMCP Protocol
Auth StandardEach API is differentUnified protocol
Permission GranularityUsually API-levelTool + parameter-level
Audit TrailScatteredCentralized logs
Rate LimitingEach implementsProtocol-level
Data PrivacyDepends on serviceProtocol-level masking
Anomaly DetectionExtra dev neededBuilt-in monitoring

So the MCP security layer is not just a "wrapper"—it’s a full security framework, providing:

  • Standardized authentication and authorization
  • Fine-grained permission control
  • Unified security auditing
  • Built-in threat detection
  • Data privacy protection

In theory, MCP offers a more secure solution than traditional API integration.