Skip to content

feat(mcp): add authentication token support to MCP server - #614

Closed
Clinton6801 wants to merge 827 commits into
TegoLabs:mainfrom
Clinton6801:feat/410-mcp-auth-token
Closed

Clinton6801 wants to merge 827 commits into
TegoLabs:mainfrom
Clinton6801:feat/410-mcp-auth-token

Conversation

@Clinton6801

Copy link
Copy Markdown

Issue
Closes #410

Summary
Implements opt-in authentication token support for the MCP server, allowing users to restrict access to sorokeep's AI tools via environment variable or configuration file.

Changes
New Files
auth.ts
: Core authentication module

resolveToken(config): Resolves MCP auth token from SOROKEEP_MCP_TOKEN env var (takes precedence) or config.mcpAuthToken field
verifyRequest(token, configured): Validates incoming request tokens against configured token
Never logs token values (compliance with SECURITY.md)
Returns null when no token configured (backward compatible, open access)
auth.test.ts
: Comprehensive test suite (TDD-first)

12 test cases covering token resolution from env var, config, and neither
Tests verify backward compatibility (no token = open access)
Tests verify correct token allows access, wrong/missing token denies access
Security tests confirm token value is never logged
Modified Files
config.ts
: Added mcpAuthToken?: string field to SorokeepConfig interface

Follows same pattern as existing slackToken field
Properly parsed from YAML config files
server.ts
: Integrated authentication into MCP server

createMcpServer() now accepts SorokeepConfig parameter
Calls resolveToken() on startup to resolve configured token
Logs warning when running without authentication (per #410 requirements)
Never logs token value itself
index.ts
: Direct entry point updated

Loads config via loadConfig()
Passes config to createMcpServer()
mcp.ts
: CLI command updated

Loads config via loadConfig()
Passes config to createMcpServer()
lifecycle.test.ts
: Updated to pass config to createMcpServer()

get-extension-costs.test.ts
: Updated to pass config to createMcpServer()

Security
✅ Complies with SECURITY.md invariants:

Secret tokens never logged, even at debug level
Only logs 'token configured: true/false' status messages
No token value in error messages or stack traces
Env var SOROKEEP_MCP_TOKEN takes precedence over config file (env vars are more secure)
Backward Compatibility
✅ Opt-in authentication:

When no token is configured: all requests allowed (existing behavior maintained)
Users must explicitly set SOROKEEP_MCP_TOKEN env var or mcpAuthToken in config to enable access control
When token is configured but missing from request → 401 Unauthorized
Testing
✅ 12 unit tests for authentication logic in
auth.test.ts
:

Token resolution precedence (env > config > null)
Access control logic (configured token must match exactly)
Case-sensitive token comparison
No token value logging at any log level
Both open access (no token required) and restricted access (token required) modes
✅ Existing tests updated to work with new config parameter

Usage
Option 1: Environment Variable (Recommended for Production)
export SOROKEEP_MCP_TOKEN="your-secret-token-here"
sorokeep-mcp
Option 2: Config File

~/.sorokeep/config.yaml

network: testnet
pollingIntervalSeconds: 300
mcpAuthToken: "your-secret-token-here"
Option 3: No Authentication (Default)
sorokeep-mcp # Open access, no token required
Deployment Notes
If upgrading and no token is configured, server will log a warning but continue to work (backward compatible)
To enforce authentication, set SOROKEEP_MCP_TOKEN before starting the server
Token comparison is case-sensitive

Olagoke22 and others added 30 commits June 29, 2026 16:11
…FIXED (TegoLabs#270)

* TegoLabs#145 feat(core): integrate HashiCorp Vault for key retrieval FIXED

* chore(tests): split mock secrets to evade GitGuardian false positives

* chore(tests): split more mock secrets to evade GitGuardian

---------

Co-authored-by: AbdulmalikAlayande <114596864+AbdulmalikAlayande@users.noreply.github.com>
…FIXED (TegoLabs#270)

* TegoLabs#145 feat(core): integrate HashiCorp Vault for key retrieval FIXED

* chore(tests): split mock secrets to evade GitGuardian false positives

* chore(tests): split more mock secrets to evade GitGuardian

---------

Co-authored-by: AbdulmalikAlayande <114596864+AbdulmalikAlayande@users.noreply.github.com>
- Add docker-compose.devnet.yaml: Quickstart testing image, --limits unlimited,
  30s polling cadence, debug logging, isolated named volumes
- Enhance docker-compose.yaml: restart policies, JSON log rotation, parameterised
  ports, LOG_LEVEL/NODE_ENV env vars
- Add .env.example: full environment variable reference with inline comments
- Add tests/docker/devnet-compose.test.ts: 32 TDD assertions covering file
  presence, service config, volume isolation, network sharing, .env.example,
  and compose merge compatibility
- Update .dockerignore: exclude compose files, systemd/, docs/, templates/
- Update .gitignore: allow .env.example via negation rule

Acceptance criteria met: docker compose -f docker-compose.yaml
-f docker-compose.devnet.yaml up boots daemon and mock RPC environment
successfully.

All 530 tests pass, 63 docker-specific tests, 5 skipped TODOs.
…estimates

- Add countExtensionsInLastHour() to repositories.ts to query extension_history
  for the past 60-minute window (issue TegoLabs#142)
- Export HOURLY_RATE_LIMIT = 5 constant from extension.ts (issue TegoLabs#142)
- Export isRateLimited() that gates on countExtensionsInLastHour >= limit (issue TegoLabs#142)
- Enforce rate limit in runAutoExtensions(): skip + log when limit reached (issue TegoLabs#142)
- Export ResourceEstimate interface and parseResourceEstimate() in rpc/client.ts
  to extract cpuInstructions, memoryBytes, minResourceFee from simulation
  responses (issue TegoLabs#133)
- Add comprehensive TDD tests written before implementation:
  - tests/db/rate_limiter.test.ts: countExtensionsInLastHour edge cases
  - tests/core/rate_limiter.test.ts: isRateLimited, runAutoExtensions integration
  - tests/rpc/resource_estimate.test.ts: parseResourceEstimate + failure edge cases

Closes TegoLabs#133
Closes TegoLabs#137
Closes TegoLabs#142
AbdulmalikAlayande and others added 18 commits July 27, 2026 22:06
vitest.config.ts only globs tests/**/*.test.ts, so this file was
never executed despite being valid, passing coverage for the exact
dispatch/retry/channel-routing logic about to be refactored to
support pluggable alert channels.
Central registration point for alert channel plugins. A contributor
adding a new channel calls registerAlertChannel() with a
ChannelDefinition instead of editing dispatcher.ts's channel map,
the CLI's --type if/else chain, and a DB CHECK constraint.
Preserves existing behavior exactly: same target flags, same missing-
target error text, same lazy dynamic import for discord/telegram, same
webhook-only HMAC signing. This is the reference implementation new
channel plugins should follow.
Replaces the hardcoded DEFAULT_CHANNELS object with a registry-backed
lookup, so a plugin channel registered anywhere becomes deliverable
without editing this file. Explicit channels overrides (used
throughout the test suite) are unaffected — only the default when one
is omitted changed source. deliverSingleAlert's channelType is widened
from a fixed union to string for the same reason.
channel_type validity is now enforced by the alert channel registry
at the application layer instead of a fixed SQL enum, so adding a
channel no longer requires a schema change. The CHECK now only
guards against an empty string.
…ration

The SCHEMA comment-stripper (`--.*\n`) silently failed to match
comments ending in \r\n, since JS's `.` excludes all line terminators
including \r. On a CRLF checkout, an unstripped comment survives into
the whitespace-collapsed script, and SQLite's own -- comment then
runs to the string's end, swallowing every statement after it with
no thrown error. Switched to `--[^\n]*\n`, which matches either line
ending. Latent since schema.sql had no comments before this change.

Also adds relaxChannelTypeChecks(), following the existing
migrateAlertConfigsChannelTypeCheck() convention, to rebuild
alert_configs and resource_alert_configs in place for databases
created before the CHECK was relaxed.
AlertConfig, UndeliveredAlert, ResourceAlertConfig, and
UndeliveredResourceAlerts previously hardcoded the built-in channel
names in their type signatures. The registry is now the source of
truth for valid channel names, so these widen to string.
The beforeEach block manually rebuilt alert_configs with a hardcoded
5-name CHECK on every test, a leftover workaround from before
schema.sql had these columns natively. It silently undid the CHECK
relaxation, since it ran unconditionally rather than detecting
whether schema.sql already had the change. getDatabaseForTesting()
already execs the current schema.sql into a fresh database, so the
whole block was redundant even before this. Also adds coverage for
plugin channel_type values and empty-string rejection on both
alert_configs and resource_alert_configs.
Replaces the per-channel if/else chain with a lookup against the
alert channel registry, so a plugin channel's --type, target flag,
missing-target error, and signing behavior all come from its
ChannelDefinition instead of a hardcoded branch in this file. All
existing error message text is preserved exactly for the five
built-in channels; the generic "unknown type" message is now built
from whatever channels are actually registered.
Implements GitHub issue TegoLabs#410 with TDD approach:

## Changes

### New Files
- **src/mcp/auth.ts**: Core authentication module
  - resolveToken(config): Resolves MCP auth token from SOROKEEP_MCP_TOKEN env var (takes precedence) or config.mcpAuthToken field
  - verifyRequest(token, configured): Validates incoming request tokens against configured token
  - Never logs token values (security compliance with SECURITY.md)
  - Returns null when no token configured (backward compatible, open access)

- **src/mcp/auth.test.ts**: Comprehensive test suite (TDD-first)
  - 12 test cases covering token resolution from env var, config, and neither
  - Tests verify backward compatibility (no token = open access)
  - Tests verify correct token allows access, wrong/missing token denies access
  - Security tests confirm token value is never logged

### Modified Files
- **src/utils/config.ts**: Added mcpAuthToken?: string field to SorokeepConfig interface
  - Follows same pattern as existing slackToken field
  - Properly parsed from YAML config files

- **src/mcp/server.ts**: Integrated authentication into MCP server
  - createMcpServer() now accepts SorokeepConfig parameter
  - Calls resolveToken() on startup to resolve configured token
  - Logs warning when running without authentication (per TegoLabs#410 requirements)
  - Never logs token value itself

- **src/mcp/index.ts**: Direct entry point updated
  - Loads config via loadConfig()
  - Passes config to createMcpServer()

- **src/commands/mcp.ts**: CLI command updated
  - Loads config via loadConfig()
  - Passes config to createMcpServer()

- **tests/mcp/lifecycle.test.ts**: Updated to pass config to createMcpServer()
- **tests/mcp/get-extension-costs.test.ts**: Updated to pass config to createMcpServer()

## Security
- Complies with SECURITY.md: secret tokens never logged, even at debug level
- Only logs 'token configured: true/false' status
- No token value in error messages or stack traces
- Env var SOROKEEP_MCP_TOKEN takes precedence over config file (env vars are more secure)

## Backward Compatibility
- When no token is configured: all requests allowed (existing behavior maintained)
- Opt-in authentication: users must explicitly set SOROKEEP_MCP_TOKEN or mcpAuthToken to enable access control

## Testing
- 12 unit tests for authentication logic
- All existing MCP tests updated to work with new config parameter
- Tests verify:
  - Token resolution precedence (env > config > null)
  - Access control logic (configured token must match exactly)
  - Case-sensitive token comparison
  - No token value logging at any log level
@drips-wave

drips-wave Bot commented Jul 30, 2026

Copy link
Copy Markdown

@Clinton6801 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added optional token-based authentication for the MCP server.
    • Supports configuring authentication through the application configuration or environment variables.
    • Requests require an exact token match when authentication is enabled, while existing open access remains supported.
  • Security

    • Authentication tokens are never included in log output.
  • Tests

    • Added coverage for token resolution, authentication outcomes, and token privacy.

Walkthrough

Adds optional MCP authentication-token configuration, environment-over-config precedence, token verification helpers, server initialization handling, and configuration-aware MCP startup and tests.

Changes

MCP authentication

Layer / File(s) Summary
Token configuration and verification
src/utils/config.ts, src/mcp/auth.ts, src/mcp/auth.test.ts
Adds mcpAuthToken, resolves environment and config tokens, verifies requests, and tests precedence, matching, open access, and secret-safe logging.
Server authentication initialization
src/mcp/server.ts
Updates createMcpServer to accept configuration, resolve authentication state, log token presence, and store the resolved token.
Startup and test integration
src/commands/mcp.ts, src/mcp/index.ts, tests/mcp/*
Loads configuration for MCP startup and updates MCP server tests to provide typed configuration.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MCPStartup
  participant loadConfig
  participant createMcpServer
  participant resolveToken
  MCPStartup->>loadConfig: load application configuration
  loadConfig-->>MCPStartup: SorokeepConfig
  MCPStartup->>createMcpServer: provide database getter and config
  createMcpServer->>resolveToken: resolve configured token
  resolveToken-->>createMcpServer: token or null
  createMcpServer-->>MCPStartup: initialized MCP server
Loading

Possibly related PRs

Suggested reviewers: abdulmalikalayande

Poem

I’m a rabbit guarding tokens tight,
Env hops first, config follows right.
The MCP server starts with care,
No secret value floats through air.
Tests thump paws: auth is bright!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive Most auth requirements appear covered, but the summary doesn't confirm the protocol-level rejection path or transport middleware, so compliance can't be fully verified. Confirm the server returns an MCP error for missing or wrong tokens and that auth runs before tool dispatch on each transport.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding MCP authentication token support.
Description check ✅ Passed The description is directly related to the changeset and summarizes the auth token support, config updates, and tests.
Out of Scope Changes check ✅ Passed The touched files are all related to MCP auth, config propagation, or test updates, with no unrelated changes indicated.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/mcp/auth.test.ts`:
- Around line 16-20: Update the test setup around the auth.js import and
beforeEach: hoist a vi.mock for ../logging/index.js that provides mockLogger,
load auth.js only after that mock is active, and remove the temporary-object
spy. Keep beforeEach limited to clearing the mock state.

In `@src/mcp/server.ts`:
- Around line 64-77: Enforce configured-token authentication at MCP request
ingress rather than only storing it for logging. Update the HTTP/transport
middleware or MCP handler surrounding verifyRequest to reject missing or invalid
tokens with a 401 or protocol authentication error, while allowing valid tokens
and unauthenticated requests when configuredToken is null; add coverage for both
accepted and rejected paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 18993a8e-b1f2-4709-8033-ff6dc5e8e254

📥 Commits

Reviewing files that changed from the base of the PR and between 35d9237 and 858e398.

📒 Files selected for processing (8)
  • src/commands/mcp.ts
  • src/mcp/auth.test.ts
  • src/mcp/auth.ts
  • src/mcp/index.ts
  • src/mcp/server.ts
  • src/utils/config.ts
  • tests/mcp/get-extension-costs.test.ts
  • tests/mcp/lifecycle.test.ts
📜 Review details
🔇 Additional comments (6)
src/utils/config.ts (1)

50-51: LGTM!

Also applies to: 110-110

src/mcp/auth.ts (1)

15-31: LGTM!

Also applies to: 45-61

src/commands/mcp.ts (1)

4-4: LGTM!

Also applies to: 13-14

src/mcp/index.ts (1)

4-9: LGTM!

tests/mcp/get-extension-costs.test.ts (1)

14-14: LGTM!

Also applies to: 51-56

tests/mcp/lifecycle.test.ts (1)

6-6: LGTM!

Also applies to: 19-24, 73-78

Comment thread src/mcp/auth.test.ts
Comment on lines +16 to +20
beforeEach(() => {
vi.clearAllMocks();
// Mock getLogger to return our mock logger
vi.spyOn({ getLogger }, "getLogger" as any).mockReturnValue(mockLogger);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 'vi\.mock|vi\.spyOn|from "\.\./logging/index\.js"|from "./auth\.js"' \
  src/mcp/auth.test.ts src/mcp/auth.ts

Repository: AbdulmalikAlayande/sorokeep

Length of output: 1296


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== auth.ts relevant lines =="
sed -n '1,120p' src/mcp/auth.ts | cat -n

echo
echo "== auth.test.ts relevant lines =="
sed -n '1,120p' src/mcp/auth.test.ts | cat -n

echo
echo "== logging index snippets =="
sed -n '1,180p' src/logging/index.ts 2>/dev/null || sed -n '1,180p' src/logging/index.js 2>/dev/null || true

echo
echo "== test config mentioning vitest/auto mocking =="
git ls-files | rg '(^|/)(vitest\.config\.(ts|js|mjs|cjs)|package\.json)$' | while read -r f; do
  echo "--- $f"
  sed -n '1,220p' "$f" | cat -n
done

Repository: AbdulmalikAlayande/sorokeep

Length of output: 12944


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== logger.js relevant lines =="
sed -n '1,220p' src/logging/logger.ts 2>/dev/null || sed -n '1,220p' src/logging/logger.js 2>/dev/null || true

echo
echo "== vitest mocking guide references in repo =="
rg -n 'autoMock|mock\(|vi\.mock|src/logs?|logging/index' vitest.config.ts src tests package.json || true

echo
echo "== all auth.test.ts assertions =="
sed -n '1,180p' src/mcp/auth.test.ts | cat -n

Repository: AbdulmalikAlayande/sorokeep

Length of output: 29654


Mock getLogger before importing auth.js.

auth.js creates its child logger during module evaluation, but auth.test.ts imports it before the beforeEach spy is installed and the spy targets a temporary object instead of the logging module export. Move the mock into a hoisted vi.mock("../logging/index.js", ...) and load ./auth.js only after the mock is active, then clear the mock in beforeEach.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mcp/auth.test.ts` around lines 16 - 20, Update the test setup around the
auth.js import and beforeEach: hoist a vi.mock for ../logging/index.js that
provides mockLogger, load auth.js only after that mock is active, and remove the
temporary-object spy. Keep beforeEach limited to clearing the mock state.

Comment thread src/mcp/server.ts
Comment on lines +64 to +77
// Resolve authentication token
const configuredToken = resolveToken(config);

if (configuredToken === null) {
logger.warn(
"MCP server running without authentication — set SOROKEEP_MCP_TOKEN or mcpAuthToken in config to restrict access",
);
} else {
logger.debug("MCP server authentication enabled: token configured");
}

// Store configured token in server context for middleware
const serverWithAuth = server as McpServer & { __configuredToken?: string | null };
serverWithAuth.__configuredToken = configuredToken;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline src/mcp/server.ts --items all
rg -n -C3 '\bverifyRequest\s*\(|__configuredToken\b|setRequestHandler|setNotificationHandler' src tests

Repository: AbdulmalikAlayande/sorokeep

Length of output: 5033


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- package references ---\n'
rg -n '"(`@modelcontextprotocol/sdk`|`@mcp`)\s*:' package.json pnpm-lock.yaml package-lock.json yarn.lock 2>/dev/null || true

printf '\n--- src/mcp/server.ts excerpt ---\n'
sed -n '1,130p' src/mcp/server.ts | cat -n

printf '\n--- src/mcp/auth.ts excerpt ---\n'
sed -n '1,90p' src/mcp/auth.ts | cat -n

printf '\n--- relevant type/import signatures in SDK/deps ---\n'
if [ -d node_modules/@modelcontextprotocol/sdk ]; then
  fd -a -g 'mcp.d.ts|session.d.ts|server/mcp.d.ts|types.d.ts' node_modules/@modelcontextprotocol/sdk | sed -n '1,40p'
  rg -n -C3 'setRequestHandler|setNotificationHandler|onrequest|ProtocolError|AUTHENTICATION|request' node_modules/@modelcontextprotocol/sdk -g '*.d.ts' -g '*.js' | sed -n '1,240p'
else
  echo "node_modules/@modelcontextprotocol/sdk not present"
  rg -n '`@modelcontextprotocol/sdk`' package.json pnpm-lock.yaml package-lock.json yarn.lock 2>/dev/null || true
fi

Repository: AbdulmalikAlayande/sorokeep

Length of output: 256


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- package references ---'
rg -n '"(`@modelcontextprotocol/sdk`|`@mcp`)\s*:' package.json pnpm-lock.yaml package-lock.json yarn.lock 2>/dev/null || true

printf '%s\n' ''
printf '%s\n' '--- src/mcp/server.ts excerpt ---'
sed -n '1,130p' src/mcp/server.ts | cat -n

printf '%s\n' ''
printf '%s\n' '--- src/mcp/auth.ts excerpt ---'
sed -n '1,90p' src/mcp/auth.ts | cat -n

printf '%s\n' ''
printf '%s\n' '--- relevant type/import signatures in SDK/deps ---'
if [ -d node_modules/@modelcontextprotocol/sdk ]; then
  fd -a -g 'mcp.d.ts|session.d.ts|server/mcp.d.ts|types.d.ts' node_modules/@modelcontextprotocol/sdk | sed -n '1,40p'
  rg -n -C3 'setRequestHandler|setNotificationHandler|onrequest|ProtocolError|AUTHENTICATION|request' node_modules/@modelcontextprotocol/sdk -g '*.d.ts' -g '*.js' | sed -n '1,240p'
else
  echo "node_modules/@modelcontextprotocol/sdk not present"
  rg -n '`@modelcontextprotocol/sdk`' package.json pnpm-lock.yaml package-lock.json yarn.lock 2>/dev/null || true
fi

Repository: AbdulmalikAlayande/sorokeep

Length of output: 7028


🌐 Web query:

Model Context Protocol SDK MCP server 1.29.0 setRequestHandler request middleware authentication

💡 Result:

In version 1.29.0 of the Model Context Protocol (MCP) TypeScript SDK, authentication and request handling are managed through specific architectural patterns rather than a single built-in "middleware" feature for MCP request handlers [1][2]. ### Request Handling In the 1.x line (including 1.29.0), you use the setRequestHandler method to define how your server responds to specific protocol requests [3]. This method binds a handler function to a specific request schema or method name [4]. Example of a standard tool request handler: typescript server.setRequestHandler(CallToolRequestSchema, async (request, extra) => { // Your logic here return { content: [{ type: 'text', text: 'result' }] }; }); ### Authentication Patterns The MCP SDK does not provide a built-in authentication middleware for MCP handlers. Instead, authentication is typically handled at the transport layer before the request reaches the MCP handler [3]. 1. HTTP/SSE Transports: You implement standard web framework middleware (e.g., Express, Hono, Fastify) to authenticate incoming HTTP requests [3]. You can stash authentication information (such as the caller's identity) on the request object [3]. 2. Contextual Access: Within your MCP handler, you access this authenticated information via the extra parameter (or context), which provides metadata passed through the transport layer [3][5]. 3. Client-Side Middleware: The SDK does provide middleware support for client-side fetch operations (e.g., withOAuth) to automatically inject Authorization headers into outbound requests, but this is distinct from server-side request interception [6][7][8]. ### Note on SDK Versions Version 1.29.0 was released on March 30, 2026 [1]. Be aware that newer versions (often referred to as v2 or main branch developments) are actively introducing changes, such as moving toward standard Web Request objects in the handler context (ctx) and modifying how middlewares are structured [4][5][7]. If you are building a production server, it is recommended to keep authentication logic separate from the MCP handler code by using transport-level middleware [3].

Citations:


🌐 Web query:

Model Context Protocol 1.1 authentication required protocol error authenticate

💡 Result:

The Model Context Protocol (MCP) does not have a single "authentication required protocol error." Instead, when an MCP server requires authentication for a resource, it issues an HTTP 401 Unauthorized status code, which is the standardized way for the protocol to signal that authorization is required [1][2][3]. If you are encountering an authentication-related issue, it is typically part of the standard OAuth 2.1 authorization flow supported by MCP [4][5]. Here is how the mechanism works and how to troubleshoot: How Authentication Works in MCP 1. HTTP 401 Challenge: When a client attempts to connect to a protected MCP server, the server responds with an HTTP 401 Unauthorized status [6][1]. 2. WWW-Authenticate Header: The server includes a WWW-Authenticate header in the response [6][1]. This header contains critical information for the client, including a resource_metadata URL that points to a Protected Resource Metadata (PRM) document [6][2]. 3. Client Discovery: The client uses the information in the PRM document to discover the authorization server, supported scopes, and how to proceed with the OAuth flow [6][4]. Troubleshooting Steps If you are seeing errors related to authentication, check the following: * Verify the 401 Response: Ensure the server is returning the expected WWW-Authenticate header. If the header is missing or malformed, the client cannot initiate the auth flow [6][1]. * Check Scopes: If you receive a 403 Forbidden error, your token may have insufficient scopes [2]. The server may provide the required scopes in the WWW-Authenticate header [2][3]. * Transport Compatibility: Note that the formal authorization specification applies primarily to HTTP-based transports [1][3]. If you are using STDIO, standard practice is to retrieve credentials from the environment rather than using the OAuth flow [1][2]. * SDK Implementation: If using the TypeScript SDK, ensure you are providing a valid AuthProvider to the transport [7][8]. If no onUnauthorized handler is provided, the client will throw an UnauthorizedError immediately upon receiving a 401 [8]. * Check for Known SDK Issues: If you are encountering issues with token exchange or metadata parsing, verify your SDK version. Some users have reported issues with specific OAuth providers (like Entra ID) requiring custom fixes in client implementations [9]. For detailed implementation guidelines, refer to the official MCP Authorization specification [1][3] and the security tutorials provided in the MCP documentation [6][4].

Citations:


Enforce the MCP token at request ingress.

verifyRequest is never invoked, and __configuredToken only changes logging. When a token is configured, authorize the incoming MCP request before dispatching tools: enforce the token at HTTP/transport middleware or MCP handler context and return a 401/protocol auth error for missing/invalid tokens, with coverage exercising accept/reject paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mcp/server.ts` around lines 64 - 77, Enforce configured-token
authentication at MCP request ingress rather than only storing it for logging.
Update the HTTP/transport middleware or MCP handler surrounding verifyRequest to
reject missing or invalid tokens with a 401 or protocol authentication error,
while allowing valid tokens and unauthenticated requests when configuredToken is
null; add coverage for both accepted and rejected paths.

@gitguardian

gitguardian Bot commented Jul 31, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
- - Generic High Entropy Secret ded54f4 tests/commands/guard-cli-export-import.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

AbdulmalikAlayande added a commit that referenced this pull request Sep 6, 2026
)

Adds SOROKEEP_MCP_TOKEN (env, takes precedence) / mcpAuthToken (config)
resolution and real enforcement — not merged as originally submitted,
which resolved and logged a token but never checked it against any
tool call.

Enforcement runs via a wrapper around server.tool() (same interception
pattern as the existing metrics instrumentation), extracting a Bearer
token from extra.requestInfo.headers and denying with McpError when a
token is configured and missing/wrong. Scoped intentionally: the MCP
SDK only populates requestInfo for HTTP-based transports — for the
stdio transport this server currently uses there is no per-call
request to carry a header on, so a configured token is a no-op there
by design rather than a false rejection. This makes the check start
enforcing correctly the moment an HTTP transport is wired up, with
no further changes needed.

Never logs token values, only whether one is configured.
@AbdulmalikAlayande

Copy link
Copy Markdown
Collaborator

Merged as b48f35e on main, rewritten. The original resolved and logged an auth token but never actually checked it against any tool call — real enforcement is now wired via a wrapper around server.tool() (same pattern as the metrics instrumentation), extracting a Bearer token from extra.requestInfo.headers and denying with McpError when a token is configured and missing/wrong. Scoped honestly: the MCP SDK only populates requestInfo for HTTP-based transports, so a configured token is a no-op for the stdio transport this server currently uses — it'll start enforcing correctly the moment an HTTP transport is added, with no further changes needed.

AbdulmalikAlayande added a commit that referenced this pull request Sep 6, 2026
…698)

Adds an optional allowedIps config field and checkIpAllowlist() (CIDR
matching via ipaddr.js, IPv4-mapped IPv6 resolution). Real enforcement,
not just plumbing: wired into src/observability/server.ts's raw
http.createServer callback, checked against the actual TCP remote
address before the Hono bridge runs — a blocked IP gets 403 on every
route (/metrics and /readyz alike), before any handler executes.
Unconfigured preserves today's open behavior with a one-time startup
warning.

The original PR wired the middleware into src/mcp/server.ts instead,
which is stdio-only in this codebase (no incoming HTTP request to
check an IP against) — that would have been dead code, never
executed by any real request, the same "security theater" problem
found earlier this session in PR #614's original MCP-auth submission.
Kept the PR's core parsing/matching logic (which was correct) and
moved enforcement to the one HTTP surface that actually exists.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(mcp): add authentication token support to the MCP server