mcpcraft-sdk

Authentication

Secure your MCP server with API keys, OAuth, and integration tokens.

Authentication

MCP servers often need to authenticate with external services or authenticate incoming requests. This guide covers both patterns.

API Key Authentication

The simplest authentication pattern — pass an API key as an environment variable.

Server-side validation

import { createServer, tool } from "mcpcraft-sdk"

const server = createServer({
  name: "api-server",
  authenticate: async (headers) => {
    const key = headers["x-api-key"]
    if (!key || key !== process.env.API_KEY) {
      throw new Error("Invalid API key")
    }
  }
})

Client configuration

Configure your MCP client to pass the key:

{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["dist/server.js"],
      "env": {
        "API_KEY": "sk-..."
      }
    }
  }
}

OAuth 2.0

For servers that act on behalf of users, implement OAuth 2.0 with a token exchange.

import { createServer, tool } from "mcpcraft-sdk"

const server = createServer({
  name: "oauth-server",
  authenticate: async (headers) => {
    const token = headers["authorization"]?.replace("Bearer ", "")
    if (!token) throw new Error("Missing token")

    // Verify token with your auth provider
    const user = await verifyOAuthToken(token)
    if (!user) throw new Error("Invalid token")

    // Attach user context for downstream tools
    return { user }
  }
})

server.add(tool({
  name: "list_repos",
  description: "List the authenticated user's repositories",
  input: {},
  run: async (_args, context) => {
    // Access the authenticated user from context
    const repos = await fetchGithubRepos(context.user.token)
    return repos
  }
}))

Service-to-Service Authentication

For machine-to-machine communication, use short-lived JWTs signed with a shared secret.

import { createServer } from "mcpcraft-sdk"
import jwt from "jsonwebtoken"

const server = createServer({
  name: "internal-server",
  authenticate: async (headers) => {
    const token = headers["authorization"]?.replace("Bearer ", "")
    if (!token) throw new Error("Missing token")

    try {
      const payload = jwt.verify(token, process.env.JWT_SECRET!)
      return { service: payload.sub }
    } catch {
      throw new Error("Invalid token")
    }
  }
})

Per-Tool Authorization

Not all tools need the same level of access. Gate sensitive operations by role or scope.

const ADMIN_TOOLS = new Set(["delete_user", "upgrade_plan"])

server.add(tool({
  name: "delete_user",
  description: "Delete a user account",
  input: {
    userId: { type: "string", description: "User ID to delete" }
  },
  run: async ({ userId }, context) => {
    if (!context.user?.roles?.includes("admin")) {
      throw new Error("Admin access required")
    }
    return deleteUser(userId)
  }
}))

Best Practices

PracticeWhy
Rotate keys regularlyLimits blast radius of leaked credentials
Use short-lived tokensReduces window for token reuse
Validate on every requestTokens can be revoked between calls
Log auth failuresDetect brute force attempts
Rate limit auth endpointsPrevent credential stuffing

Next Steps