advanced~4h

Building MCP Servers with mcp-use: Primitives, Inspector, UI & Deployment

Scaffolding an MCP server with mcp-use's project generator, declaratively exposing all 6 core primitives, debugging with the MCP Inspector, adding UI widgets via MCP-UI and OpenAI's Apps SDK, and tunneling for external access.

mcp
Speed:
MCP ClientIDE / HostClient CoreMCP ServerTools ProviderDB / Local FS
JSON-RPC 2.0 Packet Monitor:
// Server listening...
Step 1 of 7

Establish Transport pipe connection

Host client (IDE or AI assistant) connects to the server process over standard output streams.

4
Subtopics
1
Exercises
1
Projects
5
Quiz Qs
4
Flashcards
📚 Prerequisites(2)

🎓 Learning objectives

  • Scaffold a new MCP server project using mcp-use's create-mcp-use-app generator
  • Declaratively expose tools, resources, prompts, sampling, elicitation, and notifications on a server
  • Use the MCP Inspector to test and debug a server's capabilities before connecting it to an agent
  • Explain what MCP-UI and OpenAI's Apps SDK each add for servers that need visual widgets
  • Explain why tunneling is needed to expose a local development server to external clients

What is it?

This is the server-building half of the mcp-use framework: a lightweight, declarative way to define an MCP server's capabilities (tools, resources, prompts, sampling, elicitation, notifications), plus a set of supporting tools for developing, debugging, and deploying that server. Agents and clients decide how to act, but MCP servers are what make those actions possible — a server is the source of truth for capabilities, and once it exposes them in the standard MCP format, any standard MCP client can discover and use them automatically.

Why it exists

Building an MCP server from raw protocol primitives means implementing JSON-RPC message handling, capability advertisement, and the specific semantics of each of the 6 primitives yourself. mcp-use's server-side tooling exists to collapse that into small, declarative definitions — decorate a function, and it becomes a discoverable tool/resource/prompt — so the server's surface area stays simple to describe and easy for agents to discover automatically during capability negotiation. The surrounding tooling (project generator, Inspector, MCP-UI, Apps SDK integration, tunneling) exists because building a server is only step one — you also need to scaffold it quickly, test it before wiring it to a real agent, optionally give it visual UI, and eventually expose it beyond your local machine.

Problem it solves

The project generator solves the 'blank page' problem of starting a new MCP server — instead of manually setting up a TypeScript entrypoint, config files, and example capabilities, one command produces a ready-to-use starting point. Declarative capability exposition solves the boilerplate of hand-implementing each of the 6 primitives' protocol semantics. The MCP Inspector solves the 'how do I know my server actually works before connecting it to a real, possibly expensive, LLM agent' problem — it's a way to test tools, resources, and prompts directly, and watch sampling/notification events and raw JSON-RPC traffic, entirely independent of any agent. MCP-UI and the Apps SDK solve the 'my server's output is just text, but I want it to show a real widget' problem, for MCP-UI-compatible clients and ChatGPT respectively. Tunneling solves the very practical problem that a local development server isn't reachable by external clients like ChatGPT, Claude, or a mobile agent without a public URL.

Intuition

The project generator is like create-react-app or npx create-next-app for MCP servers — instead of assembling a project skeleton by hand, one command gives you a working starting point with sensible defaults. The MCP Inspector is like Postman for MCP servers — a dedicated tool to poke at your server directly and see what comes back, before you wire it into a larger, harder-to-debug system. MCP-UI and the Apps SDK are like the difference between a command-line tool and one with a real graphical interface — same underlying capability, but now with a visual surface a user can interact with directly. Tunneling is like temporarily giving your home computer a public street address so a delivery service (an external client) can actually reach it, even though it normally only has a private address on your home network.

Analogy

Think of the whole toolchain as building and shipping a small web app: the project generator is your scaffolding tool (like create-react-app), declarative primitive definitions are your route handlers, the MCP Inspector is your local dev server with a built-in API tester, MCP-UI and Apps SDK are your frontend component library for when you need more than plain text, and tunneling (like ngrok) is how you let someone outside your local network actually hit your dev server before it's properly deployed.

Technical explanation

  1. Project Generator (create-mcp-use-app): running the generator command creates a ready-to-use server with a TypeScript entrypoint, example tools/prompts/resources, configuration files, and built-in support for the MCP Inspector — a convenient starting point rather than assembling a server from nothing.

  2. Exposing MCP Capabilities: using mcp-use, a server can expose all 6 core primitives through small, declarative definitions. Tools are executable actions with side effects (e.g., a get_weather function registered via a tool decorator, invoked when the AI calls tools/call with a tool name and arguments — the server executes the function and returns structured results). Resources are read-only content exposed through a stable URI, fetchable by clients at any time during a session. Prompts are reusable instruction templates the agent can invoke to generate structured messages, letting the server supply consistent, well-formed prompts for common tasks. Sampling lets the server ask the client's model to generate text mid-workflow, useful when the server needs the model to decide, summarize, or choose between options. Elicitation requests structured input from the user (selecting an option, entering text), enabling interactive workflows where the server needs clarification. Notifications let the server push asynchronous updates (progress, status changes) to the client, ideal for long-running or multi-step operations. Together, these 6 declarative definitions cover the full MCP surface — operations, structured context retrieval, user interactions, and asynchronous messaging — and any MCP client discovers them automatically during capability negotiation, making the server instantly usable by agents without extra configuration.

  3. MCP Inspector: starting a server in development mode (npm run dev) automatically launches the MCP Inspector, a web-based dashboard for inspecting and debugging MCP servers. It lets you browse and test tools interactively, explore resources and inspect their content, preview prompts and validate arguments, watch sampling and notification events in real time, and monitor all JSON-RPC traffic between client and server — the fastest way to verify a server's capabilities before connecting it to a real agent.

  4. MCP-UI: a UI framework for MCP servers, enabling them to expose simple UI widgets that appear inside compatible clients — server health indicators, resource previews, results from recent tool calls, or debugging/introspection output — defined through a small, focused API, entirely optional but a significant developer-experience enhancement when building or testing servers.

  5. Apps SDK: OpenAI's framework for building interactive UI widgets that appear directly inside ChatGPT (or other Apps-SDK-compatible clients), written in React, letting tools return interfaces like cards, previews, or small apps rather than plain text. mcp-use simplifies adopting this: instead of manually registering widgets, writing HTML templates, configuring CSP, and bundling assets, you place a React component in the resources/ directory with a widgetMetadata export, and mcp-use scans the folder at server startup, extracts the component metadata, registers the widget as a resource (and a tool if the widget defines inputs), bundles it for the Apps SDK, applies the required CSP configuration, and provides a useWidget hook for accessing props, output, theme, and state.

  6. Tunneling: during development, MCP servers usually run on a local machine; when an external MCP client (ChatGPT, Claude, a mobile agent) needs to connect, a public URL is required — tunneling provides that public URL for a locally-running server.

Architecture

The server-building stack layers as: Project Generator (scaffolding) → Declarative Capability Definitions (the 6 primitives, each a small decorated function) → Development Server + MCP Inspector (local testing/debugging loop) → optional UI layer (MCP-UI widgets for MCP-UI-compatible clients, or Apps SDK widgets specifically for ChatGPT) → Tunneling (exposing the local server publicly once ready for external client access). Each layer is independently optional beyond the core capability definitions — a minimal server needs only the declarative primitive definitions to be functional; everything else is developer-experience or reach-extending tooling.

Workflow

  1. Scaffold a new server with the create-mcp-use-app generator to get a working TypeScript entrypoint, example capabilities, and config files.
  2. Replace or extend the example tools/resources/prompts with your actual capabilities, using the appropriate declarative decorator for each (tool, resource, prompt, sampling, elicitation, notification) based on which of the 6 primitives fits.
  3. Run the server in development mode (npm run dev) to auto-launch the MCP Inspector, and use it to browse, test, and validate every tool/resource/prompt directly, watching JSON-RPC traffic to catch issues before any agent is involved.
  4. If the server would benefit from a visual surface, add MCP-UI widgets (for general MCP-UI-compatible clients) or Apps SDK widgets specifically if targeting ChatGPT — placing a React component with a widgetMetadata export for the latter.
  5. Once the server works correctly locally, set up tunneling to expose it via a public URL if external clients (ChatGPT, Claude, mobile agents) need to connect to it.
  6. Connect a real agent (e.g., via mcp-use's client-creation flow) to the now-verified server as the final integration step.

Example

// server.ts — declaratively exposing multiple primitives with mcp-use import { createServer } from 'mcp-use/server';

const server = createServer({ name: 'weather-travel-server' });

// Tool — model-controlled action server.tool('get_weather', { description: 'Get current weather for a city', parameters: { location: 'string' }, handler: async ({ location }) => fetchWeather(location), });

// Resource — app-controlled, read-only, stable URI server.resource('docs://travel-policy', { handler: async () => readFile('./travel-policy.md'), });

// Prompt — user-controlled template server.prompt('plan_a_trip', { handler: async ({ destination }) => [ { role: 'system', content: 'You are a trip planning assistant.' }, { role: 'user', content: Plan a trip to ${destination}. }, ], });

// Sampling — server asks the client's LLM for a judgment call server.onToolCall('book_flight', async (ctx, flights) => { const choice = await ctx.sample(Pick the best flight: ${JSON.stringify(flights)}); return bookFlight(choice); });

// Elicitation — server asks the user a structured mid-task question server.onElicit('seat_preference', { schema: { type: 'string', enum: ['window', 'aisle', 'middle'] }, });

// Notifications — async progress push server.notify('booking_progress', { status: 'searching_flights' });

server.listen(); // npm run dev auto-launches the MCP Inspector alongside this

Real-world usage

Teams building internal company MCP servers (wrapping proprietary databases, ticketing systems, or internal APIs) commonly start every new server with the project generator to avoid re-solving the same scaffolding decisions repeatedly. The MCP Inspector is the standard first debugging step for any MCP server developer — verifying tool behavior directly in the Inspector before ever wiring the server to a real, costly LLM agent call is considered best practice specifically because agent-level debugging (was it the server's fault or the model's reasoning?) is much harder to disentangle after the fact. OpenAI's Apps SDK adoption is growing among companies wanting their MCP-exposed capabilities to appear as rich, interactive cards directly inside ChatGPT rather than as plain text tool results — e-commerce and travel-booking MCP servers are early, natural adopters since showing an actual product card or flight-selection widget is meaningfully better UX than a text description. Tunneling tools (like ngrok) are a standard part of any MCP server developer's local workflow the moment they need to test against a real hosted client like ChatGPT rather than only a local Inspector session.

Trade-offs

The project generator and declarative primitive definitions trade a small amount of framework lock-in for significantly faster server development — worth it for almost any new MCP server. The MCP Inspector adds zero production cost since it's purely a development-time tool. MCP-UI and Apps SDK widgets add real UI development and maintenance work (React components, widget metadata, CSP configuration) that's only worth it if the server's output genuinely benefits from a visual surface — a server that returns simple structured data (like a currency conversion) may not need a widget at all, while one returning something inherently visual (a flight-search result, a map) benefits significantly. Tunneling adds a dependency on a third-party tunneling service for local development against external clients, and generally should be replaced with a proper hosted deployment before real production use.

Visual explanation

A pipeline diagram: [create-mcp-use-app CLI] → generates → [New Server Project (TS entrypoint, example tools/prompts/resources, config, built-in Inspector support)] → developer adds → [@mcp.tool() / resource / prompt / sampling / elicitation / notification definitions] → developer runs → [npm run dev] → auto-launches → [MCP Inspector (browser dashboard): browse/test tools, explore resources, preview prompts, watch sampling/notification events, monitor JSON-RPC traffic] → once verified, optionally add → [MCP-UI widgets] or [Apps SDK widgets (React components auto-bundled for ChatGPT)] → finally, for external client access → [Tunneling: exposes localhost server via a public URL].

Advantages

  • The project generator and declarative primitive definitions dramatically reduce MCP server boilerplate compared to implementing the raw protocol

  • The MCP Inspector lets you fully verify server behavior before ever involving a real (and potentially costly) LLM agent call, isolating server bugs from agent reasoning bugs

  • MCP-UI and Apps SDK widgets let servers return rich, interactive visual results instead of being limited to plain text, when that genuinely improves the user experience

  • Tunneling makes it possible to test a local development server against real external clients (ChatGPT, Claude) before committing to a full production deployment

Disadvantages

  • Declarative primitive definitions and the project generator add a framework dependency compared to raw protocol implementation

  • Building MCP-UI or Apps SDK widgets adds real frontend development and maintenance work beyond a plain-text-returning server

  • Tunneling is a development-time convenience, not a production solution — it should not be relied on for real deployed traffic

  • The Apps SDK is specifically OpenAI/ChatGPT-targeted, so widgets built for it don't automatically work in other MCP-UI-compatible clients

Common mistakes

  • Skipping the MCP Inspector and debugging a broken tool directly through a live agent call, making it much harder to tell whether the bug is in the server or the agent's reasoning

  • Building a UI widget (MCP-UI or Apps SDK) for a server capability that would have been perfectly clear as plain text, adding unnecessary frontend maintenance burden

  • Relying on a tunneling service for real production traffic instead of migrating to a proper hosted deployment once the server is ready to go live

  • Forgetting that Apps SDK widgets are ChatGPT/Apps-SDK-specific and assuming they'll automatically render correctly in a different MCP-UI-compatible client

  • Not using the project generator's built-in Inspector support and instead trying to manually wire up debugging tooling from scratch

📂 Subtopics

📝 Quiz

💬 Deep Dive with AI