Templatetypescriptexpressadvancedโœ“ Production ReadyFreeโœ… Code Verified

Production Slack MCP Server Starter Template (TypeScript)

A production-ready Slack MCP Server built with TypeScript, Express, and the official MCP SDK. Features full Slack OAuth 2.0 authentication with workspace installation flow, Slack Web API integration, and MCP tools for channels, messages, users, and search, with Pino structured logging and rate limiting.

๐Ÿ“– 15 min readโš™๏ธ Setup: 30 minutesutilities
slackslack mcp servermcptypescriptoauthbot tokenclaudecursorproduction

Files(16)

// @ts-check
const tsPlugin = require('@typescript-eslint/eslint-plugin');
const tsParser = require('@typescript-eslint/parser');

/** @type {import('eslint').Linter.Config[]} */
module.exports = [
  {
    ignores: ['dist/**', 'node_modules/**', 'coverage/**'],
  },
  {
    files: ['src/**/*.ts'],
    ignores: ['src/__tests__/**'],
    languageOptions: {
      parser: tsParser,
      parserOptions: {
        project: './tsconfig.json',
        tsconfigRootDir: __dirname,
      },
    },
    plugins: {
      '@typescript-eslint': tsPlugin,
    },
    rules: {
      ...tsPlugin.configs.recommended.rules,
      '@typescript-eslint/no-unused-vars': 'warn',
      '@typescript-eslint/explicit-function-return-type': 'off',
    },
  },
  {
    // tsconfig.json excludes src/__tests__, so these files aren't part of
    // that project โ€” lint them without type-aware rules (project: undefined).
    files: ['src/__tests__/**/*.ts'],
    languageOptions: {
      parser: tsParser,
    },
    plugins: {
      '@typescript-eslint': tsPlugin,
    },
    rules: {
      '@typescript-eslint/no-unused-vars': 'warn',
    },
  },
];

Interactive tester

Test Your Server

Enter a deployed HTTP MCP server URL and API key to run live browser checks.

No signup

Use the public MCP endpoint, not a dashboard URL or local stdio command.

The key stays in this browser check and is sent to the endpoint you enter.

For local servers, use the local testing guide or MCP Inspector. This online tester works best with deployed HTTP MCP servers.

Production Slack MCP Server Starter Template (TypeScript)

Overview

This template provides a complete, production-ready foundation for building a Slack MCP (Model Context Protocol) server using TypeScript, Express, and the official @modelcontextprotocol/sdk. It implements the full Slack OAuth 2.0 Authorization Code flow so your server can be installed into any Slack workspace, manage bot tokens securely, and expose powerful Slack capabilities as MCP tools that Claude, Cursor, Windsurf, and other MCP-compatible clients can call.

The server ships with four Slack tool modules โ€” channels, messages, users, and search โ€” covering seven MCP tools in total, each backed by the official @slack/web-api client with proper error handling, input validation with Zod, and full TypeScript types throughout. It also includes a structured logging pipeline via Pino with request correlation IDs, per-IP rate limiting via express-rate-limit, and a /health endpoint that reports on every dependency.

Current state: this starter currently ships the server, its OAuth flow, and its test harness. A Dockerfile, docker-compose.yml, GitHub Actions CI, and a checked-in .env.example are not yet included โ€” see Project Structure and Configuration below for what's actually here and how to supply your own environment variables in the meantime.

This template is aimed at intermediate-to-advanced TypeScript developers who want to skip the boilerplate and ship a secure, observable Slack MCP integration quickly. Whether you're building an internal productivity tool for your team or a public Slack app powered by AI, this starter gives you a solid foundation to build on.

What You'll Learn

  • How to register and implement MCP tools using the official @modelcontextprotocol/sdk (McpServer + StreamableHTTPServerTransport) with full input schemas
  • How to implement the Slack OAuth 2.0 Authorization Code flow including state CSRF protection, token exchange, and in-memory token storage
  • How to use the @slack/web-api WebClient to interact with Slack's REST API for channels, messages, users, and search
  • How to validate MCP tool inputs with Zod schemas and surface structured errors back to the MCP client
  • How to set up Pino for structured JSON logging with request correlation IDs and a configurable LOG_LEVEL
  • How to implement per-IP rate limiting with express-rate-limit and return RFC 7807-style error responses
  • How to build a /health endpoint that checks each dependency (Slack API, token store) and returns machine-readable JSON
  • How to validate required environment variables at startup with a Zod-backed config schema, so misconfiguration fails fast instead of surfacing as a runtime error mid-request

Architecture

MCP Client (Claude / Cursor / Windsurf)
        โ”‚
        โ”‚  HTTP POST /mcp  (JSON-RPC 2.0)
        โ–ผ
  Express Server  โ”€โ”€โ–บ Rate Limiter (express-rate-limit)
        โ”‚
        โ–ผ
  Bearer Token Middleware  โ”€โ”€โ–บ validates Authorization: Bearer <MCP_API_KEY>
        โ”‚
        โ–ผ
  MCP SDK (McpServer)  โ”€โ”€โ–บ Tool Registry (channels, messages, users, search)
        โ”‚
        โ–ผ
  Slack Web API Client (@slack/web-api)
        โ”‚
        โ–ผ
  Slack Platform (api.slack.com)

Slack OAuth Flow (separate from /mcp, no bearer token required):
  Browser โ”€โ”€โ–บ GET /auth/slack โ”€โ”€โ–บ Slack Authorization Page
                                         โ”‚
  Browser โ—„โ”€โ”€ GET /auth/slack/callback โ—„โ”€โ”˜
        โ”‚
        โ–ผ
  In-memory Token Store (swap for Redis/DB in multi-instance deployments)

Project Structure

slack-mcp-server/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ __tests__/
โ”‚   โ”‚   โ”œโ”€โ”€ jest.setup.ts
โ”‚   โ”‚   โ””โ”€โ”€ config.test.ts
โ”‚   โ”œโ”€โ”€ auth.ts
โ”‚   โ”œโ”€โ”€ config.ts
โ”‚   โ”œโ”€โ”€ health.ts
โ”‚   โ”œโ”€โ”€ logger.ts
โ”‚   โ”œโ”€โ”€ server.ts
โ”‚   โ””โ”€โ”€ tools/
โ”‚       โ”œโ”€โ”€ channels.ts
โ”‚       โ”œโ”€โ”€ messages.ts
โ”‚       โ”œโ”€โ”€ search.ts
โ”‚       โ””โ”€โ”€ users.ts
โ”œโ”€โ”€ eslint.config.js
โ”œโ”€โ”€ jest.config.ts
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ package-lock.json
โ””โ”€โ”€ tsconfig.json

Not yet part of this template โ€” add them yourself if your deployment target needs them:

  • .github/workflows/ci.yml (GitHub Actions)
  • Dockerfile / docker-compose.yml
  • .env.example / .gitignore

Prerequisites

  • Node.js 20+ and npm 10+
  • A Slack App created at api.slack.com/apps with:
    • OAuth & Permissions: Redirect URL set to http://localhost:3000/auth/slack/callback
    • Bot Token Scopes: channels:read, channels:history, users:read, search:read, chat:write
    • Enable OAuth & Permissions and copy your Client ID and Client Secret
  • Git for version control

Quick Start

# 1. Clone or download this template
git clone https://github.com/your-org/slack-mcp-server.git
cd slack-mcp-server

# 2. Install dependencies
npm install

# 3. Configure environment
# There is no .env.example yet โ€” create .env yourself with the variables
# listed in the Configuration table below (or export them in your shell).
cat > .env << 'EOF'
SLACK_CLIENT_ID=your-client-id
SLACK_CLIENT_SECRET=your-client-secret
SLACK_SIGNING_SECRET=your-signing-secret
SLACK_REDIRECT_URI=http://localhost:3000/auth/slack/callback
MCP_API_KEY=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
SESSION_SECRET=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
EOF

# 4. Start in development mode (ts-node-dev with live reload)
npm run dev

# 5. Trigger the Slack OAuth installation flow
open http://localhost:3000/auth/slack
# Complete the OAuth flow in your browser
# Your bot token is now stored in the token store

# 6. Test the health endpoint
curl http://localhost:3000/health

# 7. Run tests
npm test

# 8. Build for production
npm run build
npm start

Features

  • Full Slack OAuth 2.0 flow โ€” CSRF-protected state parameter, token exchange, workspace-scoped in-memory token storage
  • 7 MCP tools across 4 modules โ€” slack_list_channels, slack_get_channel_history (channels); slack_send_message, slack_get_thread_replies (messages); slack_get_user_info, slack_list_users (users); slack_search_messages (search)
  • Zod input validation โ€” every tool input is validated before hitting the Slack API
  • Pino structured logging โ€” JSON output, request correlation IDs generated via uuid (falls back to an incoming x-request-id header when present), configurable LOG_LEVEL
  • Rate limiting โ€” per-IP on the /mcp endpoint with configurable window and max requests
  • Health endpoint โ€” /health checks Slack API reachability and token store state, returning 200 when healthy and 503 when down
  • Jest test harness โ€” jest.setup.ts seeds the environment variables the Zod config schema requires, so tests don't need a real .env file; ships with a config-validation test suite as a starting point (add integration tests for the /mcp and /health routes with the already-installed supertest package)
  • Full TypeScript โ€” strict mode, ESLint flat config, complete type coverage

Known limitation: SLACK_SIGNING_SECRET and SESSION_SECRET are required by the config schema but aren't wired into any logic yet โ€” there's no Slack request-signature verification middleware, and the OAuth state parameter is tracked server-side in memory rather than in a signed cookie. cookie-parser is installed but unused for the same reason. Add signature verification before exposing an Events API endpoint, and treat both secrets as reserved for that future use rather than as already-enforced protections.

Works With

  • Claude Code โ€” add as a remote HTTP MCP server:
    claude mcp add --transport http slack http://localhost:3000/mcp \
      --header "Authorization: Bearer YOUR_MCP_API_KEY"
    
  • Claude Desktop โ€” add to claude_desktop_config.json
  • Cursor โ€” add as an MCP server in .cursor/mcp.json
  • Windsurf โ€” configure in Windsurf MCP settings
  • Continue โ€” add as an MCP provider in config.json

Claude Desktop / generic HTTP MCP client configuration

{
  "mcpServers": {
    "slack": {
      "type": "http",
      "url": "http://localhost:3000/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_MCP_API_KEY"
      }
    }
  }
}

Swap the url for your deployed server's address once you're past local development. Any client that speaks MCP over Streamable HTTP with a static bearer token accepts this same shape.

Configuration

VariableDescriptionRequired
PORTHTTP server portOptional (default: 3000)
NODE_ENVRuntime environment (development/production/test)Optional (default: development)
LOG_LEVELPino log level (trace/debug/info/warn/error/fatal/silent)Optional (default: info)
SLACK_CLIENT_IDSlack App Client ID from api.slack.com/appsRequired
SLACK_CLIENT_SECRETSlack App Client SecretRequired
SLACK_SIGNING_SECRETReserved for Slack request-signature verification โ€” validated at startup but not yet checked against incoming requests (see Known Limitation above)Required
SLACK_REDIRECT_URIOAuth callback URL (must match Slack App settings); must be a valid URLRequired
SLACK_BOT_TOKENPre-existing bot token โ€” bootstraps the token store on startup, letting you skip the OAuth flow entirelyOptional
MCP_API_KEYBearer token required on the /mcp endpoint; minimum 16 charactersRequired
SESSION_SECRETReserved for future cookie-signing use โ€” validated at startup but not currently used by the OAuth flow; minimum 16 charactersRequired
RATE_LIMIT_WINDOW_MSRate limit window in millisecondsOptional (default: 60000)
RATE_LIMIT_MAXMax requests per window per IPOptional (default: 100)
BASE_URLPublic base URL of the server; must be a valid URLOptional (default: http://localhost:3000)

Deployment

There's no Dockerfile in this template yet, so deploy it like any standard Node.js HTTP server. Set the required environment variables from the table above on your platform, then run npm run build && npm start.

Railway

npm install -g @railway/cli
railway login
railway init
railway up
# Set environment variables in the Railway dashboard,
# and set the start command to: npm run build && npm start

Fly.io

Fly.io needs either a Dockerfile or Fly's Nixpacks-based buildpack detection for Node projects. Without a Dockerfile in this template, use fly launch and let it generate one, or add your own multi-stage Dockerfile before deploying:

npm install -g flyctl
fly auth login
fly launch --name slack-mcp-server
fly secrets set SLACK_CLIENT_ID=xxx SLACK_CLIENT_SECRET=xxx MCP_API_KEY=xxx SESSION_SECRET=xxx SLACK_SIGNING_SECRET=xxx SLACK_REDIRECT_URI=https://your-app.fly.dev/auth/slack/callback
fly deploy

Containerizing it yourself

If you want Docker, a minimal multi-stage setup for this project looks like: a build stage that runs npm ci && npm run build, and a runtime stage that copies dist/ plus production dependencies only (npm ci --omit=dev), runs as a non-root user, and starts with node dist/server.js. Wire /health into your container's HEALTHCHECK and your orchestrator's liveness probe.

Production Checklist

  • SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, and SLACK_SIGNING_SECRET set from Slack App dashboard
  • SLACK_REDIRECT_URI matches exactly what is registered in Slack App OAuth settings
  • MCP_API_KEY is a cryptographically random string, at least 32 bytes (e.g. openssl rand -hex 32)
  • SESSION_SECRET is a cryptographically random string separate from MCP_API_KEY
  • NODE_ENV=production is set in the deployment environment
  • LOG_LEVEL=info or warn in production (avoid debug/trace)
  • HTTPS is terminated at the load balancer or reverse proxy โ€” never HTTP in production
  • Rate limit values tuned for expected traffic (RATE_LIMIT_MAX, RATE_LIMIT_WINDOW_MS)
  • /health endpoint is wired into your load balancer or orchestrator's health checks
  • Token store replaced with Redis or a database before running more than one instance โ€” the current store is an in-process Map and does not survive restarts or scale horizontally
  • Slack App has the minimum required scopes โ€” review and remove unused ones
  • Rotate MCP_API_KEY and SESSION_SECRET on a regular schedule
  • If you plan to receive Slack Events API callbacks, add signature verification against SLACK_SIGNING_SECRET first โ€” it is not implemented in this starter

Testing

# Run all tests
npm test

# Run with coverage
npm run test:coverage

# Run tests in watch mode
npm run test:watch

# Manual curl test โ€” health endpoint
curl -s http://localhost:3000/health | jq .

# Manual curl test โ€” MCP tools/list (requires valid MCP_API_KEY)
curl -s -X POST http://localhost:3000/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'Authorization: Bearer YOUR_MCP_API_KEY' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"manual-test","version":"1.0.0"}}}'

# Manual curl test โ€” unauthenticated request should return 401
curl -s -o /dev/null -w "%{http_code}" -X POST http://localhost:3000/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

The test suite currently ships one file, config.test.ts, covering environment-variable validation. supertest is already a dev dependency โ€” use it to add integration tests against /health and /mcp (200/401/tool-call cases) as the next testing milestone.

โœ… Template ready. What's next?

  1. 1.Copy or download the template above
  2. 2.Deploy your MCP server (Railway, Vercel)
  3. 3.Test your deployment
  4. 4.Verify production readiness