Skip to content

fix: implement standard MCP protocol via @modelcontextprotocol/sdk#1

Merged
titanism merged 2 commits intoforwardemail:mainfrom
tfoote000:main
Mar 12, 2026
Merged

fix: implement standard MCP protocol via @modelcontextprotocol/sdk#1
titanism merged 2 commits intoforwardemail:mainfrom
tfoote000:main

Conversation

@tfoote000
Copy link

First off — I'm a big fan of the Forwardemail product. The service has been fantastic, and the support team has always been incredibly responsive and helpful. Thank you for all the work you put into it!

I recently discovered the MCP server and was excited to try it out with Claude Code. Unfortunately, I ran into connection failures and spent some time diagnosing the issue. I wanted to share what I found and a potential fix, but I want to be upfront: I could be wrong about the root cause, and I'm happy to be corrected if I've misunderstood something.

What I was seeing

When configuring the MCP server in Claude Code (claude mcp add), it would consistently show as failed:

$ claude mcp list
forwardemail: npx -y @forwardemail/mcp-server - ✗ Failed

To debug, I manually tested the server by piping a standard MCP initialize message:

echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | npx @forwardemail/mcp-server

The response was:

{"type":"error","id":1,"message":"Unknown request type: undefined"}

This suggested the server was expecting request.type (a custom protocol) rather than request.method (standard MCP JSON-RPC 2.0). MCP clients like Claude Code, Cursor, and Windsurf send JSON-RPC 2.0 messages with methods like initialize, tools/list, and tools/call — but the server was looking for {"type": "listTools"} and {"type": "invokeTool"} instead.

What this PR does

Replaces the custom line-delimited JSON protocol in lib/index.js with the official @modelcontextprotocol/sdk, which handles the standard MCP JSON-RPC 2.0 transport:

  • Uses the SDK's Server class with StdioServerTransport for proper stdio communication
  • Registers ListToolsRequestSchema and CallToolRequestSchema handlers via setRequestHandler
  • Maps existing toolSpec definitions to standard MCP tool format (name, description, inputSchema)
  • Reads name and version from package.json instead of hardcoding
  • Updates bin/mcp-server.js for async server startup with error handling
  • Updates all tests to use the standard MCP protocol (initialize handshake, JSON-RPC request/response format)

No changes to lib/tools.js — all 68 Forward Email API tool definitions, authentication logic, and HTTP request handling are completely untouched.

After the fix:

$ claude mcp list
forwardemail: node ~/Projects/forwardemail-mcp-server/bin/mcp-server.js - ✓ Connected

Full disclosure

  • The code changes were made with the help of Claude Code (Claude Opus 4.6). I reviewed and tested the changes, but wanted to be transparent about how they were produced.
  • I don't usually submit pull requests to open source projects, so please let me know if I've missed any process steps, if you'd prefer the changes structured differently, or if there's anything else I should do. Happy to adjust!

Checklist

  • I have ensured my pull request is not behind the main or master branch of the original repository.
  • I have rebased all commits where necessary so that reviewing this pull request can be done without having to merge it first.
  • I have written a commit message that passes commitlint linting.
  • I have ensured that my code changes pass linting tests.
  • I have ensured that my code changes pass unit tests.
  • I have described my pull request and the reasons for code changes along with context if necessary.

Test plan

  • Manual smoke test: server completes MCP initialize handshake and returns all 68 tools via tools/list
  • Manual smoke test: Claude Code shows ✓ Connected instead of ✗ Failed
  • All 19 existing unit tests updated and passing (pnpm run test)
  • Linting passes (pnpm run lint)
  • Commitlint passes on all commits

Taylor Foote and others added 2 commits March 11, 2026 13:40
The server previously used a custom line-delimited JSON protocol that
expected `{"type": "listTools"}` and `{"type": "invokeTool"}` messages.
This is incompatible with the Model Context Protocol specification,
which uses JSON-RPC 2.0 with methods like `initialize`, `tools/list`,
and `tools/call`. As a result, MCP clients such as Claude Code, Cursor,
and Windsurf could not connect to the server — the initialize handshake
would fail immediately with "Unknown request type: undefined".

Changes:
- Replace custom readline-based protocol in lib/index.js with the
  official @modelcontextprotocol/sdk Server and StdioServerTransport
- Register proper handlers for ListToolsRequestSchema and
  CallToolRequestSchema using the SDK's setRequestHandler API
- Map existing toolSpec definitions to standard MCP tool format
  (name, description, inputSchema with JSON Schema properties)
- Read name and version from package.json instead of hardcoding
- Add @modelcontextprotocol/sdk as a production dependency
- Update bin/mcp-server.js for async server startup with error handling

No changes to lib/tools.js — all 57 Forward Email API tool definitions,
authentication logic, and HTTP request handling are preserved as-is.

Tested: server now completes the MCP initialize handshake and returns
all tools via tools/list over standard JSON-RPC 2.0 stdio transport.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update all tests to use the standard MCP protocol instead of the
previous custom line-delimited JSON protocol:

- Add initializeServer() helper for the required MCP handshake
  (initialize + notifications/initialized) before any requests
- Add listTools() and callTool() helpers using JSON-RPC 2.0 format
- Update request format: type:'listTools' -> method:'tools/list',
  type:'invokeTool' -> method:'tools/call' with params wrapper
- Update response assertions: response.tools -> result.tools,
  tool.input.properties -> tool.inputSchema.properties,
  response.type:'error' -> result.isError with content[0].text
- Update 'unknown request type' test to verify JSON-RPC error response
- Remove stderr listener from sendRequest since the MCP SDK may write
  debug output to stderr without it being an error
- Fix encryptRecord test to check the plain string response directly
  instead of trying to JSON.parse it
- Add eslint-disable for unicorn/prefer-top-level-await in bin entry
  point to maintain CommonJS compatibility (top-level await triggers
  ESM reparsing warning that breaks test stderr detection)

All 19 tests pass. No changes to test coverage or assertions — same
behaviors are verified, just through the standard MCP protocol.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@titanism titanism merged commit 4815311 into forwardemail:main Mar 12, 2026
3 checks passed
@titanism
Copy link
Contributor

v1.0.5 released, thank you

https://github.com/forwardemail/mcp-server/releases/tag/v1.0.5

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.

2 participants