Borbin the 🐱

🔍 Suche...
🔍
Alle Begriffe müssen vorkommen (UND), "Phrase" für exakte Treffer, r"regex" für Muster (oder ').
  • MCP Resources: The Quiet Half of the Protocol

    📅 30. Mai 2026 · Software · ⏱️ 7 min

    Most MCP examples start with tools.

    That makes sense. Tools are easy to demonstrate: the model asks, the server does something, and a result comes back.

    Resources are quieter. They do not run an action. They expose named read access to state.

    That distinction is the point.

    MCP resources screenshot

    The screenshots show MCP Tool Explorer connected to the MCP SDK example server @modelcontextprotocol/server-budget-allocator.



    Tools Are Commands

    A tool is a command exposed by an MCP server.

    It might calculate something, start a build, create an issue, query a database, send a message, rotate an image, or trigger a deployment. A tool has input, behavior, and a result. Sometimes it also has side effects.

    That is why tool calls are the easy part of MCP to explain. They look familiar. The model asks for an operation, the host calls the server, the server replies.

    tools/call run_tests
      input:  { "suite": "unit" }
      output: { "passed": 128, "failed": 0 }

    Good tools should be explicit. They should have clear names, clear schemas, and clear errors. If a tool changes the world, that should not be hidden behind a vague description.

    So far, so normal.

    Resources Are Named Reads

    A resource is different.

    A resource is not a command. It is a URI the client can read from the server.

    resources/list
      build://latest/log
      build://latest/summary
      config://current
      trace://last-run
      device://temperature

    The server owns those names. It decides what exists, what each URI means, and what content is returned when the client calls resources/read.

    That content may come from a file. It may come from memory. It may be assembled from an API call, a database row, a device sensor, a log tail, or a generated summary. The important part is not where the bytes come from. The important part is that the operation is a read.

    Client -> Server: resources/read build://latest/log
    Server -> Client: text/plain content

    A resource can be dynamic and still be a resource. metrics://now might be different every time it is read. build://latest/log might point to whatever the latest build produced. device://temperature might be sampled live.

    Dynamic does not mean action. It means the read is resolved at request time.

    For example, a test server might expose a live-status resource. Reading it can return the server's current uptime, request counters, active configuration, or last activity. The values may change every few seconds, but the protocol shape stays the same: the client reads named state from the server.

    MCP live-status resource screenshot

    Another good example is MCP Apps. In that case, a tool can point at a ui:// resource. The client reads that resource, receives an HTML document, and renders it as the app UI. The resource is not just metadata around the tool; it is the thing that implements the interactive surface.

    That still follows the same rule: the app HTML is fetched through resources/read. The tool may trigger the experience, but the UI itself is loaded as a named read from the server.

    I wrote about the full app mechanism, sandboxing, protocol flow, and host implementation in MCP Tool Explorer Supports MCP Apps: Protocol, Code, and the Fine Print.

    The Model Does Not See Resources Automatically

    This is the part that is easy to get subtly wrong.

    Resources are not hidden context that the model magically knows about. They are server-side readable state with names.

    The client can list resources and read them from the server. It does not need to know where the data lives or how it is collected. For a log resource, the client does not know the logs. The server does. The client only knows that a named read is available.

    After reading a resource, the client can show the returned content to the user, cache it, use it internally, or include selected contents in a model request. But the model only sees the resource if the client deliberately includes that returned content.

    Until then, the resource is available to the client, not present in the model context.

    That is why the resource boundary matters. The server exposes knowledge the client does not otherwise have: logs, status, snapshots, generated reports, files, device state, or app HTML. A resource has an owner, a URI, a MIME type, a lifecycle, and a read boundary. The server says what can be read; the client asks for it by name.

    That explicit boundary is what makes the system easier to inspect and reason about.

    A Small Example

    A server could expose a build system like this:

    resources/list
      build://latest/summary
      build://latest/log
      build://latest/artifacts
    
    tools/list
      run_build
      run_tests
      deploy_preview

    The resources describe current state. The tools perform operations.

    A client might first read build://latest/summary and show it to the user. If the model needs more detail, the client can read build://latest/log and pass only the relevant part into the next request. If the user asks for a new build, the client calls run_build.

    After that, the server may update what build://latest/summary returns. It may also notify the client that the resource list or resource contents changed, depending on the server and host behavior.

    The important thing is that reading the log and starting the build are separate protocol concepts.

    One observes state. The other asks for work.

    Resource Design Is API Design

    The hard part is not implementing resources/read. The hard part is deciding what deserves a URI.

    A good resource should be specific enough to be useful, but not so tiny that the client has to stitch together a hundred fragments. It should be stable enough to reference, but not so broad that every read becomes a data dump.

    There are practical questions:

    • How current does the data need to be?
    • Can the client cache it?
    • Who is allowed to read it?
    • Does it contain secrets?
    • Should large content be summarized or paged?
    • Is this really a read, or is it a tool pretending to be harmless?

    That last one matters.

    Reading build://latest/log is a resource. Starting a build is a tool.

    Reading config://current is a resource. Changing the config is a tool.

    Reading device://temperature is a resource. Turning on a relay is a tool.

    The names are not decoration. They define where the read boundary is.

    Why This Matters

    MCP is often described as a way to give models tools. That is true, but incomplete.

    Tools expose commands. Resources expose readable state.

    That separation gives MCP clients a cleaner way to work with systems that have more going on than one request and one response. Logs can be read without starting anything. Current configuration can be inspected without changing it. A server can expose status without turning every status check into a tool call.

    It also helps humans. A resource list is inspectable. A resource URI can be logged. A resource read can be replayed. Permissions can be reasoned about at the boundary between "may read this" and "may do this".

    That is less spectacular than a flashy agent demo.

    It is also closer to how real software survives contact with Tuesday.

    The Short Version

    A tool is a command.

    A resource is a named read.

    The model does not automatically have either one. The MCP client connects to the server, discovers what the server exposes, and can then call tools or read resources by name.

    That may sound like a small protocol detail. It is not.

    It is the difference between assuming the client already knows the state and giving it a clean way to ask the server what the server knows.

  • Pike Place Fragments

    📅 27. Mai 2026 · Fotografie · ⏱️ 6 min

    On a bright Seattle day, Pike Place Market is every bit the postcard, but it keeps opening up in other directions too. The familiar sights are still there, of course, but the place also pulls toward the water, into side streets, through shop windows, and into small moments that are easy to miss. Somewhere between stairs, neon reflections, fish crates and narrow alleys, Pike Place slowly turns into something larger than its landmarks. These photos are a walk through those other sides of Pike Place, with good weather doing more than its fair share of the work.



    Before the market takes over, the waterfront has already started the conversation.

    Sunshine, harbor cranes, and a ferry coming in from the Sound: Seattle starts the day looking busy.

    1/400s f/7,1 ISO 100/21° 18-140mm f/3,5-6,3 VR f=68mm/102mm


    Puget Sound looks almost too relaxed here, considering how quickly the market picks up speed. From City View Park, this is also where Seattle lines up for its city views and panoramas.

    1/640s f/6,3 ISO 100/21° 18-140mm f/3,5-6,3 VR f=140mm/210mm


    A quiet stretch of blue, while the city keeps refusing to pause.

    1/500s f/10 ISO 200/24° 18-140mm f/3,5-6,3 VR f=140mm/210mm


    On the waterfront, something is always moving.

    1/400s f/7,1 ISO 100/21° 18-140mm f/3,5-6,3 VR f=18mm/27mm


    A closer look at the waterfront before the market takes over again.

    1/400s f/7,1 ISO 100/21° 18-140mm f/3,5-6,3 VR f=70mm/105mm


    The market edge is all stairs, rails, rooftops, and the promise of something around the corner.

    1/320s f/6,3 ISO 100/21° 18-140mm f/3,5-6,3 VR f=35mm/52mm


    The famous red sign, seen from behind, still knows exactly how famous it is.

    1/250s f/5,6 ISO 160/23° 18-140mm f/3,5-6,3 VR f=61mm/91mm


    Inside the market, everything gets closer, brighter, and slightly less interested in personal space.

    Inside, the market becomes a moving mix of lights, signs, and people with somewhere to be.

    1/125s f/3,5 ISO 900 18-140mm f/3,5-6,3 VR f=18mm/27mm


    Coffee appears here less as a beverage and more as a basic Seattle utility.

    1/125s f/5 ISO 1600/33° 18-140mm f/3,5-6,3 VR f=57mm/85mm


    A small plate taking itself exactly as seriously as an Italian deli masterpiece should.

    1/250s f/4 ISO 1100 18-140mm f/3,5-6,3 VR f=22mm/33mm


    Outside for a moment, Pike Place remembers it has a sky.

    Green space, blue sky, and the rare Seattle feeling that the weather is on your side.

    1/320s f/6,3 ISO 100/21° 18-140mm f/3,5-6,3 VR f=18mm/27mm


    A second sign rises above the hall, making sure nobody forgets where they are.

    1/400s f/7,1 ISO 100/21° 18-140mm f/3,5-6,3 VR f=18mm/27mm


    At the fish counter, Pike Place makes its case for the best seafood in the Pacific Northwest.

    A fish shop corner doing several jobs at once, with no visible management structure.

    1/160s f/3,5 ISO 400/27° 18-140mm f/3,5-6,3 VR f=18mm/27mm


    Blue signage, bright counters, and the clear sense that nobody here is under-selling the fish.

    1/125s f/3,5 ISO 450 18-140mm f/3,5-6,3 VR f=18mm/27mm


    The display is so orderly that the fish almost look like they have appointments.

    1/125s f/4 ISO 500/28° 18-140mm f/3,5-6,3 VR f=32mm/48mm


    The counter is full, but nothing here seems in a hurry.

    1/250s f/3,5 ISO 400/27° 18-140mm f/3,5-6,3 VR f=20mm/30mm


    Here the whole display gets its moment, full but still holding itself together.

    1/250s f/4 ISO 640/29° 18-140mm f/3,5-6,3 VR f=18mm/27mm


    Smoked salmon in tidy rows, with the quiet confidence of being the best there is.

    1/160s f/3,8 ISO 400/27° 18-140mm f/3,5-6,3 VR f=26mm/39mm


    Away from the big fish counter, the market becomes a collection of smaller temptations and useful excuses.

    Red umbrellas, brick, and just enough shade to make the street feel briefly organized. Hidden in plain sight is a weekend refrain that has been part of this corner for decades.

    1/250s f/5,6 ISO 110 18-140mm f/3,5-6,3 VR f=18mm/27mm


    These Italian deli masterpieces wait here before their turn on the plate, already the best reason to come back.

    1/125s f/4,2 ISO 900 18-140mm f/3,5-6,3 VR f=34mm/51mm


    Another fish counter, because Pike Place apparently believes one seafood argument is not enough. The crabs look like they are waiting for a sponge, a starfish, and a squirrel to settle the business plan.

    1/125s f/4 ISO 400/27° 18-140mm f/3,5-6,3 VR f=30mm/45mm


    This is the kind of deli where leaving empty-handed starts to look like a personal failure.

    1/250s f/4 ISO 400/27° 18-140mm f/3,5-6,3 VR f=18mm/27mm


    Soap on one side, honey on the other, and somehow the arrangement makes perfect market sense.

    1/125s f/3,8 ISO 400/27° 18-140mm f/3,5-6,3 VR f=26mm/39mm


    Every bouquet seems to be trying to outdo the one next to it.

    1/200s f/4,2 ISO 400/27° 18-140mm f/3,5-6,3 VR f=34mm/51mm


    The produce stand has enough options to make a simple decision feel optimistic.

    1/125s f/3,5 ISO 1250/32° 18-140mm f/3,5-6,3 VR f=18mm/27mm


    The shelves offer several small mysteries, most of them sealed in jars.

    1/30s f/4 ISO 1100 18-140mm f/3,5-6,3 VR f=18mm/27mm


    Dried flowers, making a quiet case for being just as beautiful.

    1/125s f/3,5 ISO 800/30° 18-140mm f/3,5-6,3 VR f=18mm/27mm


    The dried bouquets keep making their quiet case.

    1/125s f/3,5 ISO 800/30° 18-140mm f/3,5-6,3 VR f=18mm/27mm


    The neon fish sign has no interest in being subtle, which is probably for the best.

    1/125s f/5,3 ISO 720 18-140mm f/3,5-6,3 VR f=77mm/115mm


    Eventually Pike Place opens back out toward streets, water, and light.

    The Seattle Aquarium keeps watch over Puget Sound.

    1/250s f/5,6 ISO 100/21° 18-140mm f/3,5-6,3 VR f=48mm/72mm


    The waterfront drops away below, because Seattle likes its views with a little vertical planning.

    1/250s f/5,6 ISO 125/22° 18-140mm f/3,5-6,3 VR f=51mm/76mm


    Back at the main market square, Pike Place does not need the whole sign to be recognizable.

    1/250s f/5,6 ISO 200/24° 18-140mm f/3,5-6,3 VR f=18mm/27mm


    Down by the water, the city finally leaves a little space between things.

    1/500s f/8 ISO 100/21° 18-140mm f/3,5-6,3 VR f=85mm/127mm


    Green, blue, and concrete doing their best to make infrastructure look relaxed.

    1/320s f/6,3 ISO 100/21° 18-140mm f/3,5-6,3 VR f=31mm/47mm


    One last stretch of water, because some endings are better when they keep things simple.

    1/500s f/11 ISO 200/24° 18-140mm f/3,5-6,3 VR f=130mm/195mm


    Taken with my Nikon Z50II.

  • MCP Tool Explorer Supports MCP Apps: Protocol, Code, and the Fine Print

    📅 25. Mai 2026 · Software · ⏱️ 9 min

    MCP Apps adds interactive HTML UIs to MCP tools. This post covers the protocol, the host implementation, and the sandbox constraints that only show up when you actually build it.

    budget-allocator


    MCP Tool Explorer is a VS Code extension for exploring MCP servers: browsing tools, resources, and prompts, calling them, and inspecting results. With this update it also renders MCP App UIs inline, next to the regular result view.

    A few examples running inside the extension: one from the official SDK sample, five built as test cases.

    budget-allocator (official SDK sample)

    budget-allocator

    mcp.json

    "budget-allocator": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-budget-allocator", "--stdio"]
    },


    Regex visualizer: parses a regex and renders an interactive token breakdown with optional match highlighting

    regex visualizer on https://bitfabrik.io/mcp


    QR code generator: generates a QR code for any text or URL, live-updating as you type

    QR code generator on https://bitfabrik.io/mcp


    Code diff viewer: computes a line-by-line diff and renders a visual unified diff with syntax highlighting

    code diff viewer on https://bitfabrik.io/mcp


    Fractal explorer: renders a Mandelbrot or Julia set; click to zoom

    fractal explorer on https://bitfabrik.io/mcp


    Server stats dashboard: live view of uptime, call counts, and recent requests

    server stats on https://bitfabrik.io/mcp


    What MCP Apps Actually Is

    MCP tools normally return text or JSON. MCP Apps extends MCP: a tool can include a ui:// resource URI in its _meta field. The host fetches that URI, gets back a full HTML document, renders it in a sandboxed iframe, and proxies JSON-RPC 2.0 messages between the iframe and the MCP server via postMessage.

    That is the whole mechanism. The protocol is not exotic; it reuses the existing MCP resources/read call for the HTML fetch and standard JSON-RPC 2.0 for the iframe bridge. The spec explicitly notes that you do not need an SDK to implement it.

    Supported hosts as of today: Claude, ChatGPT, VS Code, Goose, Postman, MCPJam.


    The Architecture

    The moving parts:

    +------------------------------------------------------------+
    |  VS Code                                                   |
    |                                                            |
    |  +------------------------------+                          |
    |  |  Extension Host (Node.js)    |                          |
    |  |  McpToolExplorerPanel.ts     |---------- HTTP ----------> MCP Server
    |  |  McpClientManager.ts         |      (localhost:3000)    | (server.ts)
    |  +------------------------------+                          |
    |              |  postMessage                                |
    |  +-----------v------------------+                          |
    |  |  Webview (Chromium)          |                          |
    |  |  McpAppViewer.tsx (React)    |                          |
    |  |  +--------------------------+|                          |
    |  |  | iframe (sandboxed)       ||                          |
    |  |  | MCP App HTML             ||                          |
    |  |  | (postMessage only,       ||                          |
    |  |  |  no network access)      ||                          |
    |  |  +--------------------------+|                          |
    |  +------------------------------+                          |
    +------------------------------------------------------------+

    The iframe has sandbox="allow-scripts" and nothing else: no allow-same-origin, no network access, no cookies, no localStorage. McpAppViewer is the sole intermediary: it catches postMessage from the iframe and routes everything through the extension host to the real MCP server over HTTP.

    One thing that is not obvious from the spec: the bridge lives in the webview, not inside the iframe. The app sends messages to window.parent; the host catches them from the outside. Nothing is injected into the iframe.


    The Protocol

    A tool advertises its UI in _meta:

    {
      "name": "generateQrCode",
      "_meta": {
        "ui": {
          "resourceUri": "ui://mcp-test-server/qr-code"
        }
      }
    }

    The host signals support during initialize:

    new Client(
      { name: 'my-host', version: '1.0.0' },
      {
        capabilities: {
          extensions: {
            'io.modelcontextprotocol/ui': { mimeTypes: ['text/html;profile=mcp-app'] },
          },
        },
      }
    )

    After a tool call, if the result's tool definition has _meta.ui.resourceUri, the host calls resources/read on that URI, gets HTML back in content[0].text, and hands it to the webview. The iframe then runs the full MCP handshake sequence over postMessage:

    iframe → Host:  initialize
    Host  → iframe: initialize result
    iframe → Host:  notifications/initialized
    iframe → Host:  ui/initialize                 (MCP Apps extension handshake)
    Host  → iframe: ui/initialize result          (includes hostContext: theme, platform, displayMode)
    iframe → Host:  ui/notifications/initialized  ← "I am ready"
    Host  → iframe: ui/notifications/tool-input  { arguments: { ... } }
    Host  → iframe: ui/notifications/tool-result { content: [...], structuredContent: { ... } }

    After that, the iframe can call tools/call and resources/read interactively, and sends ui/notifications/size-changed to drive iframe resizing.


    Implementing the Host

    Advertising capability and fetching the HTML

    The capability is declared in the Client constructor (shown above). On the server side, attaching _meta.ui to a tool registration requires a // @ts-ignore for now: the field is protocol-level but not yet in the SDK TypeScript types:

    Without SDK:

    // @ts-ignore — _meta is not yet typed in @modelcontextprotocol/sdk
    server.registerTool('generateQrCode', {
      title: 'QR Code Generator',
      description: 'Generates a QR code for any text or URL',
      inputSchema: { text: z.string().describe('Text or URL to encode') },
      _meta: { ui: { resourceUri: 'ui://mcp-test-server/qr-code' } },
    }, handler);

    With SDK (@modelcontextprotocol/ext-apps/server), getUiCapability checks whether the connecting host supports UI before _meta is attached:

    import { getUiCapability, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
    
    const uiCap = getUiCapability(clientCapabilities);
    if (uiCap?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) {
      tool._meta = { ui: { resourceUri: 'ui://mcp-test-server/qr-code' } };
    }

    When a tool result arrives, the host reads the HTML and builds the CSP before handing it to the webview:

    case 'fetchUiResource': {
      const result = await this._clientManager.readResource(message.serverId, message.uri);
      const content = result.contents?.[0];
      const html = content && 'text' in content && typeof content.text === 'string'
        ? content.text : undefined;
      if (!html) { /* send error */ break; }
      const uiMeta = (content as any)?._meta?.ui;
      this._post({ type: 'uiResourceContent', requestId: message.requestId, html, csp: uiMeta?.csp });
      break;
    }

    The _meta.ui.csp field is optional. If the server declares external domains there (CDN hosts for scripts, API endpoints), the host adds them to the iframe's CSP. Without it, default-src 'none' applies and all external fetches are blocked. That is by design.

    function buildCsp(csp: CspMeta | undefined): string {
      const connect = csp?.connectDomains?.join(' ') ?? '';
      const resources = csp?.resourceDomains?.join(' ') ?? '';
      return [
        "default-src 'none'",
        `script-src 'self' 'unsafe-inline' ${resources}`.trimEnd(),
        `connect-src 'self' ${connect}`.trimEnd(),
        // …
      ].join('; ');
    }

    The CSP is injected as a tag prepended to the HTML before writing it to iframe.srcdoc. The iframe is shown immediately when the HTML arrives, not after the MCP handshake inside the iframe completes. Gating on that internal handshake would leave the host stuck on "Loading…" if the app is slow or broken.

    The JSON-RPC bridge

    The @modelcontextprotocol/ext-apps/app-bridge package wraps the whole handshake in a few lines:

    import { AppBridge } from '@modelcontextprotocol/ext-apps/app-bridge';
    
    const bridge = new AppBridge(iframeElement, mcpClient);
    bridge.onReady(() => {
      bridge.sendToolInput({ arguments: toolArgs });
      bridge.sendToolResult(result);
    });

    For a VS Code webview with its own React architecture, a vanilla implementation was the more practical choice: the webview already has its own message bus between extension host and UI, and adding a second one creates more complexity than it removes. The whole bridge is around 150 lines. It handles initialize, ui/initialize, tools/call, resources/read, ui/open-link, ui/notifications/size-changed, and ping. Each message type is explicit, which is useful while the protocol is still maturing.

    One detail that the sequence diagram makes clear: structuredContent must survive the entire round-trip. It is a first-class field added in MCP spec 2026-01-26; it sits alongside the plain content array and carries the machine-readable data that drives the app's UI. If any layer in the pipeline silently drops it, the iframe renders its empty state:

    // host → webview → McpAppViewer props → ui/notifications/tool-result
    if (m.type === 'toolResult') {
      sendToIframe({ jsonrpc: '2.0', id, result: {
        content: m.result, isError: m.isError,
        ...(m.structuredContent !== undefined ? { structuredContent: m.structuredContent } : {}),
      }});
    }
    Dynamic height

    The iframe cannot read its own scrollHeight without allow-same-origin. Instead, the app sends ui/notifications/size-changed with its rendered height and the host resizes the iframe element accordingly:

    <iframe
      ref={iframeRef}
      sandbox="allow-scripts"
      srcdoc={preparedHtml}
      style={{ height: iframeHeight }}
      title="MCP App"
    />


    Startup Sequence

    User clicks "Run"
           |
           v
    ToolsPanel calls Extension
    Extension calls MCP Server ──────────────────────────── HTTP POST
                                <────────── structuredContent ───────
    
    McpAppViewer mounts, sends fetchUiResource
    Extension calls resources/read("ui://…") ──────────── HTTP GET
                                              <──── HTML ────────────
    iframe.srcdoc = html   ←── iframe starts
    
    iframe                     McpAppViewer
      |── initialize ─────────────────────>|
      |<── result (ok) ────────────────────|
      |── ui/initialize ──────────────────>|
      |<── result (theme, platform, …) ────|
      |── ui/notifications/initialized ───>|
      |<── tool-input  { arguments }  ─────|   ← original args
      |<── tool-result { structuredContent}|   ← original result
      |
      renders initial state

    Interactive tool calls afterward go through the same proxy path: iframe → postMessage → McpAppViewer → extension host → HTTP → MCP server → back.


    Building an App: The QR Code Example

    The QR code generator was built without a framework: plain JavaScript, JSON-RPC 2.0 over postMessage. It exposed two sandbox constraints immediately.

    SVG data URIs are blocked. QRCode.toString(text, { type: 'svg' }) returns an SVG string. Putting it in an tag fails silently: the sandbox treats the iframe origin as null and refuses to load SVG data URIs because they can contain scripts. The fix is one API call:

    // ✗  blocked
    img.src = 'data:image/svg+xml,' + encodeURIComponent(svg);
    
    // ✓  works fine in sandboxed iframes
    const pngDataUrl = await QRCode.toDataURL(text, { width: 300 });
    img.src = pngDataUrl;

    navigator.clipboard is silently unavailable. The null origin has no clipboard permission. The fallback that still works:

    // ✗  silently fails
    await navigator.clipboard.writeText(text);
    
    // ✓  works even in sandboxed null origin
    const ta = document.createElement('textarea');
    ta.value = text;
    document.body.appendChild(ta);
    ta.select();
    document.execCommand('copy');
    document.body.removeChild(ta);

    The app-side handshake is straightforward. The SDK's React binding handles it automatically; without it:

    async function init() {
      await request('initialize', {
        protocolVersion: '2026-01-26',
        capabilities: {},
        clientInfo: { name: 'qr-code-app', version: '1.0.0' },
      });
      notify('notifications/initialized');
    
      const uiRes = await request('ui/initialize', {
        protocolVersion: '2026-01-26',
        clientInfo: { name: 'qr-code-app', version: '1.0.0' },
      });
      // uiRes.hostContext.theme → 'dark' | 'light'
    
      notify('ui/notifications/initialized');
      // host now sends tool-input and tool-result
    }


    Host Implementation Reference

    Capability Advertise extensions['io.modelcontextprotocol/ui'] in initialize
    HTML fetch Standard resources/read on the ui:// URI
    Sandbox allow-scripts only, no allow-same-origin, no allow-top-navigation
    CSP Build from _meta.ui.csp; default-src 'none' as baseline
    Bridge Handle postMessage from the webview side; nothing injected into the iframe
    structuredContent MCP spec 2026-01-26; thread it through every layer of the pipeline
    Timing Show the iframe when HTML arrives, not when the SDK handshake completes
    Resize Handle ui/notifications/size-changed to drive iframe height
    Theme Pass hostContext.theme in ui/initialize result
    CDN scripts Only if server declares the domain in _meta.ui.csp.resourceDomains


    Observations

    The protocol is simpler than it first appears. Once the architecture is clear (iframe sends to window.parent, webview catches from outside, extension host proxies over HTTP), the rest is just message routing. The non-obvious parts are the sandbox constraints (eval, SVG data URIs, clipboard all blocked without allow-same-origin) and the requirement to carry structuredContent through every layer. Both are easy to miss until something silently fails.

    The SDK and the vanilla path produce the same result. The SDK is more concise on the app side; the vanilla implementation makes every protocol message explicit, which is useful when the spec is still evolving.

    MCP Tool Explorer is available in the VS Code Marketplace. Point it at any MCP server that implements the spec; the UI appears automatically alongside the regular result view.

  • Kirschstreusel 🍒🥧

    📅 24. Mai 2026 · Fotografie · ⏱️ 3 min

    Sonntag. Ein Ablauf, der sich nicht anmeldet.

    Der Ofen ist schon auf Temperatur, bevor überhaupt entschieden ist, ob das Ergebnis dokumentiert werden soll. Der Kirschstreusel ist zu diesem Zeitpunkt noch eher ein Versprechen. Die Oberfläche unfertig, die Struktur noch im Übergang. Die Hitze beginnt langsam Ordnung zu schaffen.

    Währenddessen passiert etwas, das sich so nicht wiederholen lässt. Die Streusel verändern ihre Farbe nur wenig, von trocken zu goldbraun, mit diesem kurzen Punkt davor, an dem die Textur kippen würde. Messen kann man den Moment nicht wirklich. Man erkennt ihn eher.


    1/250s f/2,8 ISO 3200/36° 16-50mm f/2,8 VR f=31mm/47mm


    Danach kommt die Phase, in der alles noch zusammenhält. Der Kuchen bleibt in der Form, als müsste er selbst kurz prüfen, ob er stabil genug ist, um als Objekt zu existieren. Die Kirschen haben dabei ihre eigene Dynamik. Was vorher locker geschichtet war, ist jetzt gebunden, aber nicht starr. Oberfläche und Inneres passen langsam zusammen.



    Erstellt mit Focus stacking
    1/50s f/5 ISO 1000/31° 16-50mm f/2,8 VR f=33mm/49mm


    Auf dem Teller ist es wieder ein Übergangszustand. Abkühlen klingt passiv, ist es aber nicht. Dampf verschwindet, die Struktur zieht sich minimal zurück, Spannungen lösen sich. Die Oberfläche verliert etwas Glanz und gewinnt dafür an Klarheit. Ein kurzer Moment, in dem sich entscheidet, ob etwas als Bild bestehen bleibt oder nur als Erinnerung.

    Erst mit dem ersten Schnitt wird es eindeutig. Die innere Struktur wird sichtbar. Schichtung, Verteilung, kleine Unregelmäßigkeiten, die vorher unsichtbar waren. Keine perfekte Geometrie, aber eine, die funktioniert.


    und nimmt sich einen moment zeit


    Das auch mit Focus stacking
    1/50s f/5 ISO 1000/31° 16-50mm f/2,8 VR f=33mm/49mm



    und wartet kurz bevor es weitergeht


    Und das auch
    1/50s f/5 ISO 1000/31° 16-50mm f/2,8 VR f=33mm/49mm



    und passt jetzt schon fast vollständig


    und übernimmt jetzt einfach1
    1/100s f/2,8 ISO 100/21° 16-50mm f/2,8 VR f=41mm/61mm

    Kaffee trifft Kuchen ohne große Planung. Das Stück ist noch leicht warm, der Kaffee etwas zu heiß, aber das gleicht sich schnell aus. Man probiert kurz, schaut hin, und es passt. Auf dem Teller liegt noch ein zweites Stück. Erst einfach nur da, dann doch angeschnitten.

    Für einen besonderen Sonntag genau richtig. Es bleibt eben nicht bei einem Stück.


    1. Der Kuchen war diesmal pünktlich beim Shooting. Die Maske saß nicht immer, und bei der Pose gab es offenbar mehrere Meinungen.


       ↩

  • When Focus Follows the Subject

    📅 19. April 2026 · Fotografie · ⏱️ 3 min

    The cake was late for the shoot. One piece was already gone, but there was still time for a quick addition to the family photo album.

    In earlier cake sessions, the usual approach was focus stacking: several frames with different focus points, later combined into one final image. That works well, but it also takes time, and this cake was clearly not in the mood for a longer production.

    So this time the job went to a tilt adapter and a 35mm2 lens. Instead of building the result from several images, the goal was to get the whole subject sharp in a single frame.

    A tilt setup does not simply give more depth of field. What it changes is the angle of the focus plane. Instead of running straight through the scene, the sharp area can be tilted to follow the subject. For something photographed from the side, that makes a real difference. The sharpness no longer has to run mainly from front to back; it can follow the shape of the cake much more naturally.

    That is what makes this so interesting. This cannot really be done in software without looking fake. The actual tilt effect has to come from the optics.

    A quick text test

    A simple text card as a test subject. With the tilted setup, the whole card stays sharp even though it sits at an angle. The current shooting angle is already very close to the limit of the setup, and at f/2 it gives a good impression of what the tilt adapter can do.

    1/40s f/2 ISO 320/26° f=35mm


    The Cake

    Here is the actual subject. One piece of the round cake is already missing, because cakes do not always wait patiently for the photographic process to begin.

    Even the sugar coating tells part of the story, with a few visible traces of a rather hurried arrival.

    1/40s f/2 ISO 800/30° f=35mm


    Same angle, no tilt: focus at the front The same view, but with the tilt set to zero. Focus is placed on the front part of the cake, and the rest falls away much more quickly.

    1/40s f/2 ISO 320/26° f=35mm


    Same angle, no tilt: focus at the back Again the same angle and no tilt, but this time focused farther back. The difference is easy to see, and it shows quite nicely what the tilt setup changes.

    1/40s f/2 ISO 320/26° f=35mm


    The Setup

    The full setup with the cake on the table and the camera floating in the air, carefully aligned and locked onto the target.

    1/25s f/4 ISO 400/27° 16-50mm f/2,8 VR f=25mm/37mm


    Tilt adapter and 35mm lens

    A closer look at the camera with the tilt adapter and the 35mm f/2 lens. A small addition, but one that changes the way this kind of image can be made.

    1/30s f/3,2 ISO 400/27° 16-50mm f/2,8 VR f=33mm/50mm

    If you look closely, you can still see the fine sand from the Sarasota beaches on the camera. This sand is everywhere. The camera bag did not escape either.

    Back on the Coffee Table

    Once the optics had done their job, the cake could finally continue with the coffee part of the story.

    1/30s f/2,8 ISO 320/26° 16-50mm f/2,8 VR f=40mm/60mm



    1. Nikon Nikkor Ai 35mm f2 ↩

Seite 1 von 55 Ältere Beiträge →
ÜBER

Jürgen E
Principal Engineer, Villager, and the creative mind behind lots of projects:
Windows Photo Explorer (cpicture-blog), Android apps AI code rpn calculator and Stockroom, vrlight, 3DRoundview, BitBlog and my github


Blog-Übersicht Chronologisch

KATEGORIEN

Auto • Electronics • Fotografie • Motorrad • Paintings • Panorama • Software • Uncategorized


Erstellt mit BitBlog!