How to Integrate MCP Servers Into Your Application
Connect MCP servers to your application with the right transport, capability discovery, authorization, security controls, and production error handling.
To integrate an MCP server, your application acts as an MCP client: choose a transport, create a client, connect so the SDK completes the initialization handshake, discover tools, prompts, and resources, then route those capabilities into your application or model. Use stdio when your application launches a local server process. Use Streamable HTTP for a remote server or a server mounted in a web application. Add authorization at the HTTP boundary when the server is protected, control the environment inherited by local processes, and close the client during shutdown.
The official TypeScript SDK summarizes the core design as “A Client plus one transport is a complete MCP client.” Read the connection guide for the SDK version you deploy.
1. Decide whether your application is an MCP client or server
An application that connects to an existing MCP server implements the client role. It starts or reaches a server, negotiates a protocol version, discovers capabilities, and invokes them. An application that exposes its own functions to MCP-aware clients implements the server role. The official Go SDK documents both roles and their lifecycle and transport layers: MCP Go SDK overview.
This guide covers the client integration path. Keep the boundary explicit: your application decides which discovered capabilities are available to a model or user, validates arguments, invokes the server, and handles the result.
2. Choose the transport
| Deployment | Transport | What to handle |
|---|---|---|
| Your app launches a local server | stdio | Own the child process lifecycle, pass a controlled environment, and keep protocol traffic on standard streams. |
| Server is remote or mounted in a web app | Streamable HTTP | Configure the endpoint, authorization, timeouts, and session behavior required by the server. |
| Target only supports an older SSE endpoint | Legacy SSE fallback | Prefer Streamable HTTP first; add SSE compatibility only when the server requires it. |
The C# transport documentation covers stdio and Streamable HTTP details, including process and session considerations: C# SDK transports. The TypeScript v1 documentation describes SSE as a legacy option and recommends trying Streamable HTTP before falling back: TypeScript client documentation.
3. Connect with the TypeScript SDK
Install the client SDK and the transport dependencies used by your application:
npm install @modelcontextprotocol/sdk
Local server over stdio
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const client = new Client({
name: "example-application",
version: "1.0.0"
});
const transport = new StdioClientTransport({
command: "node",
args: ["./mcp-server.js"],
// Pass only variables the server needs.
env: {
PATH: process.env.PATH ?? "",
NODE_ENV: "production"
}
});
try {
await client.connect(transport);
console.log("Connected to MCP server");
const tools = await client.listTools();
console.log(JSON.stringify(tools, null, 2));
const result = await client.callTool({
name: "lookup",
arguments: { query: "MCP" }
});
if (result.isError) {
throw new Error(`MCP tool failed: ${JSON.stringify(result)}`);
}
console.log(result);
} finally {
await client.close();
}
connect() performs initialization and leaves the negotiated protocol version, server capabilities, and instructions available through the client. Do not assume a tool exists until discovery confirms it.
Remote server over Streamable HTTP
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const client = new Client({
name: "remote-integrator",
version: "1.0.0"
});
const transport = new StreamableHTTPClientTransport(
new URL(process.env.MCP_SERVER_URL ?? "https://example.com/mcp"),
{
requestInit: {
headers: {
Authorization: `Bearer ${process.env.MCP_ACCESS_TOKEN}`
}
}
}
);
try {
await client.connect(transport);
const { tools, prompts, resources } = {
tools: await client.listTools(),
prompts: await client.listPrompts(),
resources: await client.listResources()
};
console.log({ tools, prompts, resources });
} finally {
await client.close();
}
Use the exact transport constructor and options supported by the SDK version in your lockfile. Remote servers can require a session, subscriptions, or server-to-client requests; choose session behavior to match those features and your deployment.
4. Discover and expose capabilities deliberately
After connection, discover only what you need:
listTools()returns tool names, descriptions, and JSON Schema input definitions.listPrompts()exposes reusable prompt templates.listResources()exposes readable resources; use the client’s resource-read API when you need their contents.
Map tool schemas into your model provider’s tool definitions. When the model selects a tool, validate the name and arguments against the discovered schema, call the MCP tool, and return the result to the conversation. Your application remains the policy layer: apply allowlists, user permissions, rate limits, and confirmation requirements before invocation.
Tool routing pattern
async function invokeModelTool(client, allowedTools, name, args) {
if (!allowedTools.has(name)) {
throw new Error(`Tool is not allowed: ${name}`);
}
// Add JSON-Schema validation here before invoking the server.
const result = await client.callTool({ name, arguments: args });
return {
isError: result.isError === true,
content: result.content
};
}
The first-client guide explains the discovery and invocation flow: Build your first MCP client. A tool failure can be returned as an ordinary result with isError: true; surface that state to your application instead of treating every response as success.
5. Add authorization for protected HTTP servers
For a protected remote server, authenticate at the HTTP boundary. Servers should verify bearer tokens on every request. Clients can use the SDK’s OAuth helpers when the server requires an authorization flow. Preserve issuer information and validate the authorization-server identity before redeeming an authorization code, as required by the current specification announcement: 2026-07-28 MCP specification.
- Keep access tokens in a secret store, never in source control or tool arguments.
- Send credentials through the transport’s supported authorization mechanism.
- Validate token audience, issuer, expiry, and scopes on the server.
- Handle 401 and 403 responses separately from tool-level errors.
- Rotate credentials without requiring a code change.
The Go lifecycle documentation covers authorization and protocol behavior: MCP Go protocol documentation.
6. Secure local stdio integrations
A child process launched over stdio can inherit the parent process environment. That may expose cloud credentials or API keys to an untrusted server. The C# SDK documentation calls out this specific risk. Construct an explicit environment allowlist, use an absolute executable path where practical, validate the server package and version, and keep protocol messages on stdout while sending diagnostics to stderr.
const safeEnv = {
PATH: process.env.PATH ?? "",
HOME: process.env.HOME ?? "",
MCP_SERVER_CONFIG: "/etc/my-app/mcp-server.json"
};
const transport = new StdioClientTransport({
command: "/usr/local/bin/my-mcp-server",
args: [],
env: safeEnv
});
7. Sessions, compatibility, and shutdown
HTTP session behavior depends on whether you need subscriptions, server-to-client requests, or per-client isolation. Review the server and SDK documentation before selecting stateless or session-aware operation. The PHP SDK notes that session handling matters when a server runs across multiple processes: PHP SDK server guidance.
For older SSE-only servers, attempt Streamable HTTP first and fall back only when the endpoint explicitly requires SSE. Confirm that both sides support the same protocol version and transport before deployment.
Close the client and transport during normal shutdown and process signals. This releases child processes and network sessions and prevents orphaned servers.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Connection hangs during startup | Wrong command, endpoint, or transport | Run the server manually, verify the URL, and confirm whether it supports stdio, Streamable HTTP, or legacy SSE. |
| Initialization reports an unsupported version | Client and server protocol versions do not overlap | Upgrade the older side or configure the SDK’s supported-version behavior according to its documentation. |
| No tools appear | Discovery was skipped, permissions hide tools, or the server exposes prompts/resources only | Call the relevant list method after connect() and inspect the negotiated capabilities. |
Tool result has isError: true |
The server rejected arguments or failed while executing | Log the structured result, validate arguments against the discovered schema, and show a recoverable error to the model or user. |
| HTTP 401 or 403 | Missing, expired, incorrectly scoped, or wrongly issued token | Refresh credentials, verify issuer and audience, and check required scopes. |
| Local server receives unexpected secrets | Inherited parent environment | Pass an explicit environment allowlist to the stdio transport. |
| Protocol messages are corrupted | Server logs were written to stdout | Write diagnostics to stderr and reserve stdout for MCP protocol traffic. |
| Works locally but fails behind a load balancer | Session affinity or shared session storage is missing | Use the SDK’s documented session strategy and provide shared state when multiple processes handle one client. |
9. Performance, reliability, and cost
- Startup: Reuse a connected client for multiple calls when the server and workload permit; repeatedly spawning a stdio process adds process startup and initialization overhead.
- Concurrency: Bound simultaneous tool calls and apply per-server timeouts. A slow or unavailable server should not block unrelated application requests.
- Payloads: Discover schemas once per connection and avoid sending large resource contents through a model when a smaller derived result is sufficient.
- Retries: Retry only transport failures that are safe to repeat. Do not blindly retry side-effecting tools unless the server exposes an idempotency mechanism.
- Observability: Record connection duration, capability discovery failures, tool latency, status, and structured error results without logging tokens or sensitive arguments.
- Cost: MCP itself does not define your model, hosting, or third-party service pricing. Account separately for model tokens, server infrastructure, network transfer, and any tool provider charges.
10. Or skip the browser setup
If your MCP workflow needs website screenshots, ScreenshotNeo provides an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. You can connect it through your MCP client and let an agent request captures without building browser automation.
ScreenshotNeo also offers a direct API. See the ScreenshotNeo API documentation for all options.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Before capture, cookie and consent banners, newsletter popups, and chat widgets are removed. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; the response identifies the result with X-Page-Verdict and X-Billed headers. You can also use full-page capture with lazy images loaded, CSS-selector element capture, device presets, custom JavaScript and CSS, waits, blocking rules, headers, cookies, geolocation, PDFs, caching, signed links, async webhooks, bulk capture, and the usage API. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
FAQ
Do I need an MCP server for every integration?
No. MCP is useful when a service exposes tools, prompts, or resources through the protocol. A conventional REST or SDK integration may be simpler for a fixed, strongly typed operation.
Should I use stdio or HTTP for a desktop application?
Use stdio when the desktop app installs and launches a local server. Use Streamable HTTP when the capability is hosted remotely or shared by multiple clients.
Can a model call MCP tools directly?
Your application should mediate the call. Discover schemas, expose an allowlisted subset to the model, validate arguments, enforce permissions, invoke the MCP tool, and return the structured result.
What happens if a server adds a new tool?
Refresh capability discovery according to the server’s session behavior and update the model tool definitions. Never assume a previously cached schema remains current forever.


