MCP Apps ( MCP-UI) — serwer zwraca interfejs, nie tekst

przez Łukasz | cze 15, 2026

Resources, Tools, Prompts — to trzy prymitywy MCP, które opisała ta seria.

Jest czwarty. Czas nadrobić.

SEP-1865 wszedł do specyfikacji MCP 28 stycznia 2026. Nazywa się MCP Apps i definiuje, jak serwer MCP zwraca interaktywny interfejs użytkownika zamiast surowego tekstu. Przez kilka miesięcy opisywaliśmy wszystko, co dzieje się między klientem a serwerem — JSON-RPC, transport, prymitywy, OAuth, tutorial PHP. Ten wpis domyka obraz.

Czym jest MCP Apps

Kto z kim: serwer MCP → host (klient MCP), przez nowy typ zasobu.

Problem który rozwiązuje: narzędzia MCP zwracają tekst. Tekst jest czytelny dla modelu, ale ubogi dla człowieka. Wykres, formularz, dashboard, interaktywna wizualizacja — tego tekstem nie zrobisz. MCP Apps wprowadza standardowy sposób, w którym serwer może zwrócić HTML w sandboxowanym iframe, a host go wyrenderuje bezpośrednio w interfejsie.

Jak: serwer deklaruje zasoby UI pod schematem ui://, narzędzia linkują te zasoby przez metadane w polu _meta.ui.resourceUri, host renderuje je w izolowanym iframe. Komunikacja między iframe a hostem idzie przez JSON-RPC — ten sam protokół, który poznałeś w artykule o JSON-RPC pod spodem.

Format treści: text/html;profile=mcp-app — HTML który host rozpoznaje jako zasób MCP Apps i renderuje inaczej niż zwykłą stronę.

Transport: bez zmian — Streamable HTTP albo stdio, tak jak reszta MCP. MCP Apps to rozszerzenie, nie nowy protokół.

Kto stworzył: inicjatywa community (Ido Salomon i 8 współautorów), zainspirowana projektem MCP-UI i Apps SDK od OpenAI. Merged do głównej spec przez Anthropic 28.01.2026. Pod Linux Foundation jak cały MCP.

Stan adopcji: wczesny, ale konkretny. Early adopters przed standaryzacją: Postman, HuggingFace, Shopify, Goose, ElevenLabs. Po merge: otwarte implementacje w oficjalnych SDK — TypeScript (gotowy), Ruby (gotowy), Python, Go, Rust, Java, Kotlin, Swift, PHP — każdy ma otwarte issue pod SEP-1865. VS Code ma issue z 5 czerwca 2026. Apollo Client, LibreChat — w trakcie integracji.

Gdzie nie działa: MCP Apps wymaga hosta, który explicite obsługuje wyrenderowanie iframe. Stary klient MCP bez tej obsługi zignoruje _meta.ui.resourceUri i zwróci tylko tekstowy wynik narzędzia — wsteczna kompatybilność jest zachowana.

Jak to działa od środka

Trzy elementy składają się na jedno wywołanie z UI.

1. Zasób UI na serwerze

Serwer deklaruje zasób HTML pod schematem ui://:

typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';
import { createUIResource } from '@mcp-ui/server';
import { z } from 'zod';

const server = new McpServer({ name: 'my-server', version: '1.0.0' });

// Zasób UI — HTML pod schematem ui://
const widgetUI = createUIResource({
  uri: 'ui://my-server/widget',
  content: {
    type: 'rawHtml',
    htmlString: '<h1>Wyniki kampanii</h1><p>CTR: <strong>3.2%</strong></p>'
  },
  encoding: 'text',
});

// Rejestracja zasobu
registerAppResource(server, 'widget_ui', widgetUI.resource.uri, {}, async () => ({
  contents: [widgetUI.resource]
}));

2. Narzędzie które linkuje zasób przez _meta

typescript
// Narzędzie linkuje zasób UI przez _meta.ui.resourceUri
registerAppTool(server, 'get_campaign_results', {
  description: 'Pobierz wyniki kampanii jako interaktywny widget',
  inputSchema: { campaign_id: z.string() },
  _meta: { ui: { resourceUri: widgetUI.resource.uri } }  // ← tu jest połączenie
}, async ({ campaign_id }) => {
  // Wynik tekstowy dla modelu (zawsze obecny)
  return {
    content: [{ type: 'text', text: `Kampania ${campaign_id}: CTR 3.2%, konwersje 142` }]
  };
});

3. Klient renderuje przez AppRenderer

typescript
import { AppRenderer } from '@mcp-ui/client';

function ToolUI({ client, toolName, toolInput, toolResult }) {
  return (
    <AppRenderer
      client={client}
      toolName={toolName}
      sandbox={{ url: sandboxUrl }}
      toolInput={toolInput}
      toolResult={toolResult}
      onOpenLink={async ({ url }) => {
        if (url.startsWith('https://') || url.startsWith('http://')) {
          window.open(url);
        }
      }}
    />
  );
}

Wynik: model dostaje tekst (jak zawsze), człowiek widzi HTML w bezpiecznym iframe.

Dlaczego _meta, nie nowy typ treści

To jest nieoczywisty wybór architektoniczny, który warto rozumieć. MCP Apps nie dodaje czwartego elementu do content[]w wynikach narzędzia. Zamiast tego używa _meta — pola zarezerwowanego w JSON-RPC dla niestandardowych metadanych, które model ignoruje.

Powód: model nie renderuje UI. To aplikacja-host go renderuje. Mieszanie treści dla modelu z treścią dla interfejsu w jednej tablicy content[] byłoby złym pomysłem — host musiałby odgadywać, co jest dla kogo. _meta.ui.resourceUri jest jednoznaczne: to wskazówka dla hosta, nie dla modelu.

Stary klient który nie zna MCP Apps ignoruje _meta i dostaje czysty wynik tekstowy. Nowy klient który zna MCP Apps sięga po zasób UI i renderuje iframe. Jeden serwer, dwa typy klientów, bez breaking change.

Model bezpieczeństwa

Sandbox jest tu kluczowy i nieprzypadkowy. Każdy zasób UI działa w izolowanym iframe z ograniczonymi uprawnieniami. Komunikacja między iframe a hostem idzie wyłącznie przez JSON-RPC — loggowalne, audytowalne, bez bezpośredniego dostępu do DOM rodzica. Serwer nie może wyciągnąć danych z hosta bez jawnego wywołania narzędzia, które przejdzie przez normalny mechanizm autoryzacji MCP.

To przedłużenie modelu bezpieczeństwa, który opisaliśmy w artykule o OAuth i autoryzacji: host kontroluje co agent może zrobić, tu host kontroluje też co iframe może zrobić.

Co to znaczy dla builderów PHP i WordPress

PHP SDK ma już otwarte cztery issues pod implementację SEP-1865 (335, 350, 351, 352 w modelcontextprotocol/php-sdk). To oznacza, że serwer MCP napisany w PHP — jak ten z tutorialu na webflux — będzie mógł zwracać zasoby UI przez te same mechanizmy co TypeScript, gdy PHP SDK wdroży spec.

Dziś: możesz już zwracać _meta.ui.resourceUri w odpowiedzi narzędzia ręcznie — to zwykłe pole JSON. Host który obsługuje MCP Apps odbierze je poprawnie. Natywna abstrakcja w SDK przyjdzie z implementacją.

Gdzie to żyje — dwa repozytoria

modelcontextprotocol/ext-apps — oficjalna spec i SDK rozszerzeń. Tu jest definicja registerAppTool, registerAppResource i pełna spec MCP Apps w specification/draft/apps.mdx.

github.com/idosal/mcp-ui — community playground, pakiety @mcp-ui/client i @mcp-ui/server (npm), Ruby gem, PyPI. Tutaj powstała większość wzorców zanim weszły do spec; tu też trafiają eksperymenty z nowymi typami treści poza HTML.

Relacja między nimi jest taka jak między spec a implementacją referencyjną — ext-apps to standard, mcp-ui to plac zabaw i punkt wejścia dla builderów.

Co z tego wynika

MCP Apps to czwarty prymityw — obok Resources, Tools i Prompts — który serwer może wystawić hostowi. Różni się od trzech poprzednich jedną rzeczą: nie jest dla modelu. Jest dla człowieka, który patrzy w ekran. Model dostaje tekst, człowiek dostaje iframe.

Specyfikacja jest w main od 28 stycznia. SDK-i wdrażają. Jeśli budujesz serwer MCP i chcesz, żeby jego wyniki wyglądały jak narzędzie a nie jak ściana tekstu — tu zaczyna się ta droga.


Pojęcia ze słownika: MCP · Prymitywy MCP · JSON-RPC · Streamable HTTP · OAuth dla agentów

Pakiety: @mcp-ui/client · @mcp-ui/server · mcp-ui-server (PyPI) · mcp_ui_server (Ruby)

Spec: modelcontextprotocol/ext-apps · SEP-1865 PR

Table of Contents

Wykorzystanie MCP: Agentic Quest

Wykorzystanie MCP: Agentic Quest

◈ Agentic Quest Ile wiesz o Agentic Web?Sprawdź się w 5 rundach. NEXUS — AI narrator zasilany słownikiem Agentic Web — opisuje pojęcie. Ty zgadujesz. Im szybciej trafisz, tym więcej punktów. 278 pojęć, losowe zagadki, żadna runda się nie powtarza. 278 pojęć w słowniku...

Wykorzystanie MCP: Krzyżówka

Wykorzystanie MCP: Krzyżówka

◈ Agentic Web Crossword Krzyżówka z Agentic Web —nowa układanka przy każdym odświeżeniu. Słownik Agentic Web liczy 278 pojęć z 23 klastrów tematycznych. Przy każdym starcie losujemy dwa klastry, wybieramy pojęcia i budujemy unikalną krzyżówkę — żadna sesja się nie...