npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@e0ipso/drupal-bridge-mcp

v2.1.2

Published

Minimal Drupal MCP Server - 336 lines vs 6000 lines (94% reduction!)

Readme

Minimal Drupal MCP Server

336 lines vs 6000 lines - A radically simplified Model Context Protocol server for Drupal integration

🎯 The Transformation

This MCP server was dramatically simplified based on research of MCP TypeScript implementations:

  • Before: 5,955 lines across 25+ files
  • After: 336 lines in 1 file
  • Reduction: 94% smaller code
  • Functionality: Identical features

✨ Features

  • 🔐 OAuth 2.1 Authentication - Simple token-based auth with Drupal
  • 📚 Content Access - Search tutorials, load/create nodes
  • 🚀 MCP Standard - Follows official MCP patterns
  • 📦 Minimal Dependencies - Only MCP SDK required
  • 🎯 Zero Bloat - No custom error handling, logging, or validation

🚀 Quick Start

Prerequisites

  • Node.js 20+
  • Drupal site with OAuth 2.0 configured

Installation

npm install @e0ipso/drupal-bridge-mcp

Configuration

Create a .env file:

DRUPAL_BASE_URL=https://drupalize.me
AUTH_ENABLED=true

Usage

# Run directly
npx drupal-bridge-mcp

# Or install globally
npm install -g @e0ipso/drupal-bridge-mcp
drupal-bridge-mcp

🛠️ Available Tools

search_tutorials

Search Drupal tutorials and educational content

{
  "keywords": "views",
  "types": ["tutorial", "course"],
  "drupal_version": ["10", "11"],
  "limit": 10
}

load_node

Load a Drupal node by ID

{
  "nodeId": "12345"
}

create_node

Create a new Drupal node

{
  "type": "article",
  "title": "My New Article",
  "body": "Article content...",
  "status": true
}

test_connection

Test connection to Drupal server

{}

📊 Implementation Comparison

| Aspect | Before | After | | ------------------ | ------------------------- | -------------------- | | Lines of Code | 5,955 | 336 | | Files | 25+ | 1 | | Dependencies | 20+ packages | 1 package | | Error Handling | Custom 500-line system | MCP SDK built-in | | Validation | Custom 300-line framework | MCP SDK schemas | | Logging | Complex Pino system | Simple console.error | | Authentication | 1,500-line OAuth system | 50-line OAuth client | | Configuration | 200-line env parser | Direct env access |

🏗️ Architecture

The minimal server consists of just 3 classes:

// 1. Simple OAuth Client (~50 lines)
class SimpleOAuth {
  async getToken(): Promise<string>; // Token management
}

// 2. Drupal JSON-RPC Client (~80 lines)
class DrupalClient {
  async searchTutorials(params): Promise<any>;
  async loadNode(id): Promise<any>;
  async createNode(params): Promise<any>;
  async testConnection(): Promise<boolean>;
}

// 3. MCP Server (~200 lines)
class MinimalDrupalMcpServer {
  // Tool registration and handling using MCP SDK
}

The server includes detailed request/response logging using the debug package. Enable debug output by setting the DEBUG environment variable:

# All debug output
DEBUG=mcp:* npm start

# Only incoming MCP requests
DEBUG=mcp:request:in npm start

# Only outgoing requests to Drupal
DEBUG=mcp:request:out npm start

# Or run in development mode
DEBUG=mcp:* npm run dev

Debug Namespaces:

  • mcp:bootstrap - Server startup and tool discovery
  • mcp:request:in - Incoming MCP requests (method, path, headers, session ID, body)
  • mcp:request:out - Outgoing requests to Drupal (URL, headers, body, response)

Note: Authorization tokens are automatically redacted in debug output for security.

🔄 Session Management & Reconnection

The server implements a robust session management system that enables clients (like MCP Inspector) to reconnect without re-authentication.

Architecture

User-Level Token Storage: Tokens are stored by user ID (extracted from JWT), not by transport session ID:

Map<userId, tokens>; // Persistent user-level tokens
Map<sessionId, userId>; // Ephemeral session-to-user mapping

OAuth Token Extraction: The server automatically extracts OAuth tokens from Authorization: Bearer headers in browser-based authentication flows, enabling MCP Inspector and other clients to authenticate using standard OAuth 2.1 authorization code flow with PKCE.

Session Lifecycle

  1. Authentication: User authenticates → JWT decoded → userId extracted → tokens stored
  2. Session Close: Transport disconnects → session mapping removed → tokens preserved
  3. Reconnection: New session ID → same userId from JWT → existing tokens reused
  4. Explicit Logout: User logout → tokens removed from storage

Key Distinction: Session close ≠ Logout

  • Disconnecting preserves your authentication
  • Only explicit logout removes tokens

Debug Endpoints

Monitor session state during development:

Health Check - Shows active users and sessions:

curl http://localhost:3000/health

Debug Sessions - Detailed session mappings:

curl http://localhost:3000/debug/sessions

Troubleshooting

Problem: Tool calls return 403 after reconnection Solution: Check if tokens persisted across reconnection:

  1. Call /health to see activeUsers count
  2. Call /debug/sessions to verify session → user mapping
  3. Check server logs for "Token lookup failed" messages

Problem: Multiple reconnections create duplicate users Solution: This should not happen - JWT extraction ensures same userId reused. If seeing this:

  1. Verify JWT contains valid sub, user_id, or uid claim
  2. Check logs for "User reconnecting - reusing existing tokens"

🔐 OAuth Scope Management

The MCP server automatically discovers required OAuth scopes from Drupal's tool definitions at startup, eliminating manual scope configuration.

How It Works

  1. Server fetches tool definitions from /mcp/tools/list
  2. Extracts required scopes from each tool's annotations.auth.scopes
  3. Combines discovered scopes with any additional scopes from OAUTH_ADDITIONAL_SCOPES
  4. Requests combined scope set during OAuth authentication

This eliminates manual scope configuration and automatically adapts to backend tool changes.

Automatic Scope Discovery

Scopes are extracted from tool metadata:

{
  "name": "examples.contentTypes.create",
  "annotations": {
    "auth": {
      "scopes": ["content_type:write"],
      "level": "required"
    }
  }
}

The server automatically requests the content_type:write scope during OAuth flow.

Adding Extra Scopes

Use OAUTH_ADDITIONAL_SCOPES to request scopes beyond what tools declare:

# .env
OAUTH_ADDITIONAL_SCOPES="admin:access experimental:features"

Common use cases:

  • Administrative or debugging scopes not tied to specific tools
  • Experimental features in development
  • Cross-domain permissions
  • Future-proofing for upcoming features

Supported formats:

# Space-separated
OAUTH_ADDITIONAL_SCOPES="admin:access experimental:features"

# Comma-separated
OAUTH_ADDITIONAL_SCOPES="admin:access, experimental:features"

Tool Access Validation

Before invoking a tool, the server validates:

  1. Authentication - Tool requires auth and session is authenticated
  2. Scopes - Session has all required OAuth scopes
  3. Permissions - Drupal validates permissions server-side

Error example:

Insufficient OAuth scopes for tool "examples.contentTypes.create".
Required: content_type:write
Missing: content_type:write
Current: profile, content_type:read

This error means the OAuth access token doesn't include the content_type:write scope. To resolve:

  1. Check that the Drupal OAuth server supports the required scope
  2. Re-authenticate to obtain a new token with the correct scopes
  3. Verify tool metadata correctly declares required scopes

Scope Discovery Logs

Check server startup logs to see discovered scopes:

✓ Discovered 15 tools from Drupal
  Extracted 8 scopes from tool definitions
  Additional scopes: admin:access
  Total scopes: admin:access, content:read, content:write, content_type:read, profile, ...

This transparency helps debug scope-related issues and verify configuration.

Authentication Levels

Tools can declare three authentication levels:

  • none: Public tools, no authentication required
  • optional: Enhanced functionality with auth, but works without
  • required: Enforces authentication and scope validation

If a tool doesn't declare annotations.auth, it defaults to level='none' (public access).

🎓 Key Learnings

What MCP SDK Provides (That We Were Building Custom):

  • JSON-RPC Transport - Built-in STDIO transport
  • Error Handling - Automatic JSON-RPC error responses
  • Input Validation - Works seamlessly with JSON schemas
  • Type Safety - Full TypeScript support
  • Tool Registration - Simple handler pattern

OAuth Simplified:

  • OAuth Required - Drupal headless APIs need authentication
  • Complex Discovery - Simple client_credentials flow sufficient
  • PKCE/Stateless - Unnecessary complexity for MCP servers

📋 Development

# Install dependencies
npm install

# Run in development
npm run dev

# Build for production
npm run build

# Type check
npm run type-check

🧪 OAuth E2E Testing

Overview

The OAuth e2e test suite validates the complete OAuth authentication flow using the MCP Inspector CLI. These tests verify session management, token persistence, and authenticated tool execution against a real Drupal OAuth server.

Note: These tests require manual execution due to external Drupal server dependencies and interactive OAuth approval steps.

Prerequisites

  • Node.js 20+
  • Access to a Drupal instance with OAuth 2.0 server configured
  • OAuth client credentials (client ID and optional secret)
  • MCP Inspector CLI (installed as dev dependency)

Setup

  1. Copy environment configuration:

    cp .env.test.example .env.test
  2. Configure OAuth server settings in .env.test:

    DRUPAL_BASE_URL=https://your-drupal-site.com
    AUTH_ENABLED=true
    E2E_TEST_TIMEOUT=120000
  3. Install dependencies:

    npm install

Running the Tests

Execute the OAuth e2e test suite:

npm run test:e2e:oauth

Expected Test Flow:

  1. ✅ Connection validation
  2. ✅ OAuth flow initiation
  3. ⏸️ Manual OAuth approval (interactive pause)
  4. ✅ Reconnection after OAuth callback
  5. ✅ Token association verification
  6. ✅ Authenticated tool execution
  7. ✅ Session state validation

The test will pause at step 3 and prompt you to complete OAuth authorization in your browser. Press Enter after approving to continue.

Test Architecture

The e2e tests use @modelcontextprotocol/inspector CLI mode to programmatically interact with the MCP server:

  • Executes Inspector commands via child process
  • Validates JSON responses with assertions
  • Detects the documented OAuth token association bug
  • Verifies session lifecycle and token persistence

For detailed test specifications, see OAuth Flow Test Methodology.

Troubleshooting

Issue: "Missing required environment variables"

  • Solution: Ensure .env.test file exists with all required variables from .env.test.example

Issue: "Connection failed to Drupal server"

  • Solution: Verify DRUPAL_BASE_URL is correct and server is accessible
  • Check: curl https://your-drupal-site.com/health

Issue: "OAuth authorization failed"

  • Solution: Verify OAuth client credentials are correct in Drupal admin
  • Check: Client ID matches the one configured in Drupal OAuth settings

Issue: "Tool execution returns 403 Forbidden"

  • This is the expected bug the test is designed to detect
  • Indicates OAuth tokens are not properly associated with reconnected sessions
  • See methodology document for root cause analysis

Issue: "Test timeout exceeded"

  • Solution: Increase E2E_TEST_TIMEOUT in .env.test (default: 120000ms)
  • Complete OAuth approval more quickly when prompted

Issue: "Inspector CLI not found"

  • Solution: Run npm install to ensure dev dependencies are installed
  • Verify: npx @modelcontextprotocol/inspector --help

Supported Inspector Version

This test suite is compatible with @modelcontextprotocol/inspector version 0.15.0.

📂 Backup

The original 6000-line implementation is preserved in /backup/ for reference, demonstrating how enterprise-level complexity can emerge when simple patterns would suffice.

📄 License

Proprietary

👨‍💻 Author

Mateu Aguiló Bosch (e0ipso) [email protected]