MCP Server Integration Problems: Protocol, Transport, Permissions, and Schema
A layer-by-layer MCP server diagnostic guide covering protocol era, transport, runtime, authorization, tool schema, and a safe Inspector CLI test.
When an MCP server will not connect or a tool call fails, do not change the client configuration, server code, and permissions at the same time. Identify the failing layer first. Use this order: protocol version → transport → startup and environment → permissions/auth → inputSchema → one read-only call.
As of August 23, 2026, the current verified MCP specification version is 2026-07-28. It no longer requires the old initialize handshake: a client can use server/discover, while the protocol version, client information, and capabilities travel with requests in _meta. Legacy implementations using 2025-11-25 or earlier follow a different sequence: initialize, the server response, and then notifications/initialized. Never combine these two protocol eras in one exchange.
Separate MCP from the model API at the outset. MCP connects a client to tools and context; the model request can use a different route and different credentials. Isolate the model layer with the BetterToken guide: your own API Key and a selected OpenAI-compatible or Anthropic-compatible interface provide a separate, testable route, so you do not have to search for a model error inside MCP. That Key is not an MCP server credential, and BetterToken is not an MCP host or MCP transport.
Classify the failure quickly
Before opening Inspector, record one symptom and the last confirmed checkpoint:
- the process does not start at all;
- the process runs, but the client receives no JSON-RPC;
- the transport responds, but the version or capabilities do not agree;
- the server returns
401or403; tools/listworks, but the expected tool is missing;- the tool is visible, but
tools/callrejects its arguments; - the call completes, but its result cannot be verified.
Do not put an API Key, bearer token, cookie, complete prompt, or private file contents in your notes. For correlation, the timestamp, server name, method, JSON-RPC id, error code, and a sanitized message are enough.
1. Establish the protocol era
Find out which version the client, server, and SDK support. A Connected message confirms the transport and part of discovery, but it does not prove agreement on 2026-07-28.
For a modern implementation, check three signals:
- The SDK or its release notes explicitly state support for
2026-07-28. - The trace contains
server/discoveror another discovery path defined by the SDK. - Requests contain valid
_metawith the version, client information, and capabilities.
Do not manually copy the shape of _meta from another SDK. A compatible client or official SDK must generate the exact wire format. If the server waits for initialize while the client sends self-contained 2026-07-28 requests, the problem is a protocol-era mismatch, not a tool schema error.
Legacy initialize: only for 2025-11-25 and earlier
This is a minimal legacy request. Do not add it to a modern 2026-07-28 flow “just in case.”
After a successful response, the legacy client sends notifications/initialized. If that exchange breaks, compare versions and the capabilities list first. It is too early to proceed to tools/list.
2. Test the transport separately from MCP semantics
MCP defines methods and data, while the transport handles startup, framing, delivery, and request cancellation. Switching from stdio to HTTP will not repair an invalid inputSchema.
stdio
With stdio, the client launches the server as a child process. Messages pass through stdin and stdout as newline-delimited UTF-8 JSON-RPC documents.
Check that:
commandexists and runs as the same user.- Arguments are passed as separate items without relying on shell aliases.
- The working directory contains the required files, or paths are absolute.
- Required variables are actually available to the child process.
stdoutcontains no banner, debug lines, or stack trace; logs go tostderr.
One stray console.log() on stdout can break framing before the client sees a JSON-RPC response.
Streamable HTTP
With Streamable HTTP, the client sends POST messages to one MCP endpoint. A response can be plain JSON or request-scoped SSE. Verify the exact URL, HTTP method, Content-Type, TLS, redirects, proxy, and authentication method.
Run the transport test on loopback or in an isolated test environment. Do not scan a public production endpoint without permission. If POST returns an HTML login page, a 301/302 to another host, or a reverse-proxy response, you have not reached MCP yet.
3. Reproduce startup in the same environment
For stdio, run the server command directly from the same directory and as the same user as the MCP client. An IDE launch is not an equivalent test because PATH, cwd, runtime, and permissions may differ.
Check:
pwd does not reveal a secret by itself, but do not publish the path if it contains a user name or a private project name. The server command should either wait for JSON-RPC on stdin or exit with a clear error on stderr. An immediate exit without a message usually indicates a wrong entry point, a missing dependency, or a startup error that was handled without logging.
As of August 23, 2026, the official Inspector CLI documentation requires Node.js 22.19.0 or newer. If the version is lower, stop and switch runtimes before continuing the diagnosis.
4. Separate permissions from authentication
The response code determines the next check:
401 Unauthorized— the credential is missing, expired, or rejected;403 Forbidden— the identity is recognized but lacks the required permission or scope;404— this often means the endpoint or route is wrong, not that permissions are missing;- timeout — the server, proxy, or tool did not finish in time; it is not proof of an authentication error.
Do not disable permissions for a smoke test. Create a separate test identity with the minimum scope and select a read-only tool without external effects. The client should leave a human able to reject the call; tool annotations are untrusted data and do not replace policy.
Keep the authentication decision (allowed/denied), scope name, and correlation ID in logs. Remove or mask the credential itself, the Authorization header, and cookies.
5. Validate the capability and inputSchema
The server must declare the tools capability before serving tools/list. Every tool needs a unique name and a valid JSON Schema object in inputSchema. Arguments sent to tools/call must match that schema.
A minimal read-only tool declaration:
Common mistakes are straightforward: no root type: object, a required field missing from properties, the client sends a number instead of a string, an argument name uses the wrong case, or the server advertises two tools with the same name.
After the version and transport agree, test the method with this JSON-RPC payload:
Then call exactly one tool:
These snippets show method payloads, not a complete connection bootstrap. In a 2026-07-28 flow, a compatible client adds the required request metadata in _meta; in a legacy flow, initialize happens first. Do not send these JSON snippets manually to a production endpoint without permission.
6. Run a safe test with MCP Inspector CLI
First install Inspector as a pinned project dependency from a trusted lockfile. The --no-install command below will not download an arbitrary current version during diagnosis.
List tools on a local stdio server:
Call echo once:
For a test Streamable HTTP endpoint on loopback:
Do not put a token in shell history, a URL, or an article. If the endpoint requires authentication, configure the credential through Inspector’s normal mechanism in the local environment, or stop the test and request a test identity from the server owner. The commands above intentionally contain no real Key.
Symptom → check → fix
Acceptance criteria
The integration passes the minimum acceptance test only when all five conditions hold:
- Logs or telemetry show the expected negotiated protocol version.
- The transport preserves framing: stdio has no extra
stdout, and HTTP responds from the MCP endpoint. tools/listreturns one expected tool with a validinputSchema.tools/callis actually invoked withtext=MCP_OK_2026and returnsMCP_OK_2026unchanged.- The test did not disable permissions, expose credentials, or trigger an external side effect.
Seeing MCP_OK_2026 in a model response is not enough. You need a recorded tool call with the matching JSON-RPC id, or an Inspector record plus a sanitized server log.
Stop conditions
Stop the diagnosis and do not move to the next layer if:
- the protocol era supported by either side is unknown;
- Inspector wants to install an unpinned package version without review;
- the test requires a production credential, disabled authentication, or broader scope;
- the only available tool writes to a database, sends a message, changes a file, or runs a command;
- the HTTP endpoint belongs to a third party and permission to test it is unconfirmed;
- a Key, token, cookie, personal data, or private resource content appears in logs;
tools/listis unstable or returns different schemas between repeated runs;- the server crashes before producing a valid JSON-RPC response.
In these cases, preserve the sanitized symptom, client/server/SDK versions, transport, correlation ID, and a minimal error fragment. That is enough to hand the problem to the owner of the correct layer without distributing unnecessary permissions or secrets.
Sources
The specification version, transport details, and minimum Node.js version were verified on August 23, 2026. Check the current official documentation again before repeating the diagnosis after updating the client, server, SDK, or Inspector.