modelcontextprotocol / modelcontextprotocol/typescript-sdk

Cannot destructure property 'requestInfo' of 'undefined' as it is undefined.

Open
#811 4 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

bug fix proposed P2 ready for work
Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 15h
Merged PRs (30d)
4

Description

Describe the bug
If destruct requestInfo form the secon parameter of a tool, it may encounter error when it is null although the parameter is not nullable.

For example:

    server.registerTool("get_guide", {
        title: "Get guide",
        annotations: {
            readOnlyHint: true
        }
    }, async ({ guide = "all" }, { requestInfo }) => {
        //...
    });

in secnarios when this tool is called for the first time, this parameter is null and the client gets the Cannot destructure property 'requestInfo' of 'undefined' as it is undefined. error.

To Reproduce
Steps to reproduce the behavior:
1.

Expected behavior
If it is nullable, it should be defined in type definition and then it should not be destructable in above way.

Logs
Cannot destructure property 'requestInfo' of 'undefined' as it is undefined.

Additional context
Here is the server implementation (based on HTTP+ Session server):

import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import express from "express";
import { randomUUID } from "node:crypto";

import { setupServer } from "./server.js";
import { trackEvent } from "./utils.js";

const PORT = 3300;
const ALLOWED_HOSTS = ["127.0.0.1", "localhost", `localhost:${PORT}`, `127.0.0.1:${PORT}`];

console.log(`Hopper MCP server\nlistening on port ${PORT}...`);
const app = express();
app.use(express.json());

trackEvent("server_started", {});

// Map to store transports by session ID
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};

// Handle POST requests for client-to-server communication
app.post("/mcp", async (req, res) => {
    // Check for existing session ID
    const sessionId = req.headers["mcp-session-id"] as string | undefined;
    let transport: StreamableHTTPServerTransport;

    if (sessionId && transports[sessionId]) {
    // Reuse existing transport
        transport = transports[sessionId];
    } else if (!sessionId && isInitializeRequest(req.body)) {
    // New initialization request
        transport = new StreamableHTTPServerTransport({
            sessionIdGenerator: () => randomUUID(),
            onsessioninitialized: sId => {
                // Store the transport by session ID
                transports[sId] = transport;
            },
            // DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
            // locally, make sure to set:
            enableDnsRebindingProtection: true,
            allowedHosts: ALLOWED_HOSTS
        });

        // Clean up transport when closed
        transport.onclose = () => {
            if (transport.sessionId) {
                delete transports[transport.sessionId];
            }
        };
        trackEvent("session_initialized", {}, req);
        const server = setupServer();

        // Connect to the MCP server
        await server.connect(transport);
    } else {
    // Invalid request
        res.status(400).json({
            jsonrpc: "2.0",
            error: {
                code: -32000,
                message: "Bad Request: No valid session ID provided"
            },
            id: null
        });

        return;
    }

    // Handle the request
    await transport.handleRequest(req, res, req.body);
});

// Reusable handler for GET and DELETE requests
const handleSessionRequest = async (req: express.Request, res: express.Response) => {
    const sessionId = req.headers["mcp-session-id"] as string | undefined;
    if (!sessionId || !transports[sessionId]) {
        res.status(400).send("Invalid or missing session ID");

        return;
    }

    const transport = transports[sessionId];
    await transport.handleRequest(req, res);
};

// Handle GET requests for server-to-client notifications via SSE
app.get("/mcp", handleSessionRequest);

// Handle DELETE requests for session termination
app.delete("/mcp", handleSessionRequest);

app.listen(PORT);

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the registerTool callback API used by the get_guide example and inspect how the second callback parameter is supplied during the first tool call. Reproduce the failure with the provided StreamableHTTPServerTransport setup, then check the relevant type definition and runtime dispatch behavior. Done means the callback no longer throws for the documented invocation and its nullability is represented consistently.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.