Most MCP tutorials stop at the server. You write a Python or TypeScript MCP server that exposes tools, test it with the CLI inspector, and call it done. But production agent applications need client orchestration, multi-server coordination, authentication boundaries, structured output parsing, and often a UI for humans to supervise tool calls. MCP-USE is a framework that addresses the deployment gap by providing both sides of the protocol boundary in a single stack.
MCP defines a protocol for tool discovery and execution. It does not define how to build an agent loop, manage state across multiple servers, handle authentication, or render interactive UIs. MCP-USE fills those gaps with:
The framework is opinionated. It assumes you want to run TypeScript clients that talk to one or more MCP servers (Python or TypeScript), and it provides batteries-included patterns for authentication, schema validation, and structured output.
MCP-USE splits responsibilities across three layers:
The protocol boundary is stdio or HTTP. MCP servers communicate over JSON-RPC, so the client and server can run in different languages, different processes, or different machines. MCP-USE provides SDKs for both sides but does not require you to use both. You can write a Python server and connect it to a custom TypeScript client, or vice versa.
MCP is stateless at the protocol level. Each tool call is independent. But agent applications often need session state (conversation history, user context, intermediate results). MCP-USE handles this in two ways:
This separation prevents race conditions. The client owns orchestration state. The server owns tool execution state. Neither tries to synchronize mutable state across the boundary.
MCP-USE supports both TypeScript and Python for server development, but the client is TypeScript-only. This asymmetry reflects real-world deployment patterns:
The protocol boundary makes this work. The client does not care what language the server is written in. It only cares about the JSON-RPC interface.
Running TypeScript and Python in the same application stack introduces operational complexity:
| Aspect | TypeScript-Only | Python-Only | Mixed Stack |
|---|---|---|---|
| Dependency management | npm | pip/poetry | npm + pip |
| Runtime requirements | Node.js | Python 3.9+ | Both |
| Deployment surface | Single container | Single container | Multi-container or monorepo |
| Debugging complexity | Low | Low | Medium (cross-language traces) |
| Library ecosystem | Limited ML tooling | Limited web tooling | Best of both |
For prototypes, a mixed stack is fine. For production, consider whether you can consolidate. If your tools are simple HTTP calls or file operations, a TypeScript-only stack reduces moving parts. If your tools require heavy Python libraries, run the client and server in separate containers and use HTTP transport instead of stdio.
MCP distinguishes between tools and resources:
send_email(to, subject, body). user_profile, recent_transactions.
MCP-USE exposes both through the client SDK. Tools are called during the agent loop. Resources are fetched before the loop starts or on-demand when the LLM requests context.
This distinction matters for caching and observability. Resources can be cached aggressively because they are read-only. Tools cannot be cached because they have side effects. MCP-USE logs tool calls but not resource fetches, which keeps traces focused on actions rather than data access.
MCP-USE provides a simple agent loop in TypeScript:
import { MCPClient } from 'mcp-use';
import { OpenAI } from 'openai';
const client = new MCPClient();
await client.connectToServer('python-server', {
command: 'python',
args: ['server.py'],
transport: 'stdio'
});
const tools = await client.listTools();
const openai = new OpenAI();
let messages = [{ role: 'user', content: 'Send an email to alice@example.com' }];
while (true) {
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages,
tools: tools.map(t => t.schema)
});
const choice = response.choices[0];
if (choice.finish_reason === 'stop') break;
const toolCall = choice.message.tool_calls[0];
const result = await client.callTool(toolCall.function.name, JSON.parse(toolCall.function.arguments));
messages.push(choice.message);
messages.push({ role: 'tool', tool_call_id: toolCall.id, content: JSON.stringify(result) });
}
This loop is not production-ready. It has no error handling, no retry logic, no timeout, and no observability. But it shows the plumbing:
MCP-USE does not enforce a specific agent framework. You can plug this loop into LangChain, LlamaIndex, or a custom orchestrator. The framework only handles the MCP protocol boundary.
Production agents often need tools from multiple domains: database queries, API calls, file operations, email, Slack. Each domain can be a separate MCP server. MCP-USE clients can connect to multiple servers and merge their tool schemas:
await client.connectToServer('db-server', { command: 'python', args: ['db_server.py'] });
await client.connectToServer('email-server', { command: 'python', args: ['email_server.py'] });
const allTools = await client.listTools(); // Merged from both servers
The client routes tool calls by name. If two servers expose tools with the same name, the client throws an error. You must namespace tool names to avoid collisions (db.query, email.send).
This introduces a coordination problem: how do you manage dependencies between tools? If the LLM calls db.query and then email.send, and the email tool needs data from the query result, the orchestrator must pass that data through the message history. MCP servers cannot call each other directly. All coordination happens in the client.
MCP-USE's TypeScript SDK includes a feature called MCP Apps: React components that render UI driven by MCP server state. This is not part of the MCP spec. It is a framework-specific extension.
An MCP App is a TypeScript client that:
This is useful for supervised agents. Instead of running the agent loop autonomously, you render a UI where a human approves each tool call before execution. The UI shows the tool name, arguments, and expected result schema. The human clicks "approve" or "reject."
Most agent frameworks treat the UI as an afterthought. You build the agent, then bolt on a chat interface. MCP Apps invert this: the UI is a first-class client of the MCP protocol. The same server that powers an autonomous agent can also power a supervised UI.
This matters for compliance and debugging. If your agent makes financial transactions or modifies production data, you want a human in the loop. MCP Apps provide that without requiring you to rewrite the server.
MCP servers run in separate processes. If the server needs to access authenticated APIs (Stripe, GitHub, AWS), it must handle credentials. MCP-USE does not provide a built-in auth system. You have three options:
token argument on every tool.
Option 3 is the most secure for production. The client never sees credentials. The server validates the session ID before executing tools.
MCP-USE does not enforce any of these patterns. It only provides the transport layer. You must implement auth yourself.
MCP-USE logs tool calls and results to stdout by default. For production, you need structured logging and distributed tracing. The framework provides hooks for custom loggers:
client.onToolCall((toolName, args) => {
console.log(`[TOOL CALL] ${toolName}`, args);
});
client.onToolResult((toolName, result) => {
console.log(`[TOOL RESULT] ${toolName}`, result);
});
These hooks let you integrate with OpenTelemetry, Datadog, or a custom tracing backend. The client does not emit traces by default because it does not know your tracing setup.
For debugging, MCP-USE includes a web-based inspector that shows:
The inspector runs as a separate web server. You point it at your client, and it proxies all MCP traffic through a UI. This is useful for debugging protocol issues or understanding what the LLM is requesting.
MCP-USE includes a CLI inspector that lets you test servers without writing client code:
npx mcp-use inspect python server.py
This starts an interactive REPL where you can:
The CLI inspector is useful for unit testing servers. You can write shell scripts that call tools and assert on the results.
MCP tools declare input and output schemas using JSON Schema. MCP-USE validates arguments before sending them to the server and validates results after receiving them. If validation fails, the client throws an error instead of passing invalid data to the LLM.
This prevents a class of bugs where the LLM generates malformed tool calls and the server crashes. The client catches the error and can retry with corrected arguments.
Schema validation also enables type-safe clients. The TypeScript SDK generates TypeScript types from JSON schemas, so you get autocomplete and compile-time checks when calling tools programmatically.
MCP-USE is not an agent framework. It is a protocol client. You can integrate it with LangChain, LlamaIndex, or any other orchestrator by writing a thin adapter:
import { MCPClient } from 'mcp-use';
import { Tool } from 'langchain/tools';
class MCPTool extends Tool {
constructor(private client: MCPClient, private toolName: string) {
super();
}
async _call(args: string): Promise<string> {
const result = await this.client.callTool(this.toolName, JSON.parse(args));
return JSON.stringify(result);
}
}
const client = new MCPClient();
await client.connectToServer('server', { command: 'python', args: ['server.py'] });
const tools = (await client.listTools()).map(t => new MCPTool(client, t.name));
// Pass tools to LangChain agent
This adapter wraps each MCP tool in a LangChain Tool instance. The agent framework does not know it is talking to MCP. It just sees a list of tools.
MCP-USE introduces several failure modes:
| Failure | Impact | Mitigation |
|---|---|---|
| Server process crash | All tools from that server become unavailable | Run servers in separate containers with health checks and auto-restart |
| Stdio buffer overflow | Client hangs waiting for server response | Use HTTP transport for high-throughput servers |
| Schema mismatch | Client sends invalid arguments or cannot parse results | Version your tool schemas and validate on both sides |
| Authentication token expiry | Tools fail with 401 errors | Implement token refresh in the server or client |
| Multi-server tool name collision | Client cannot route tool calls | Namespace tool names ( server_name.tool_name ) |
The biggest operational risk is the dual-language runtime. If you deploy TypeScript and Python in the same container, a Python dependency conflict can break the entire stack. Use separate containers or a monorepo with isolated dependency trees. The stdio transport also introduces a single point of failure: if the server process hangs, the client cannot detect it without implementing custom timeouts.
MCP-USE is a good fit when:
Avoid MCP-USE when: