Skip to content
| Marketplace
Sign in
Visual Studio Code>Education>LF MCP DemoNew to Visual Studio Code? Get it now.
LF MCP Demo

LF MCP Demo

Markus van Kempen

| (0) | Free
Control plane and test harness for the mcp-ticket-demo MCP server — lifecycle management, step-by-step diagnostics, end-to-end CRUD test, send-to-chat prompts, and one-click IDE config for VS Code, Bob, Cursor, and Windsurf.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

LF MCP Demo

VS Code Marketplace Open VSX npm npm downloads License GitHub Node.js MCP

A control plane and test harness for the mcp-ticket-demo MCP server. The extension manages the server lifecycle, writes IDE config, runs a step-by-step diagnostic, scores tool calls end-to-end, and fires canned prompts directly into your LLM chat — all from a sidebar in VS Code, IBM Bob, Cursor, or Windsurf.

The bundled MCP server (mcp-ticket-demo) is a fully functional support-ticketing backend with 10 real tools, three auth modes, API key management, per-tool gates, rate limiting, and a live /admin dashboard. It runs as a local stdio child process, a local HTTP server, a Podman container, or a shared Code Engine endpoint — the extension switches between them without touching a config file by hand.

Companion to MCP as a Platform · MCP Dev Summit Toronto · Oct 2026 — every lesson in the talk is a live, reproducible demo in this repo. · Talk slides


What is included

Extension (VS Code / Bob / Cursor / Windsurf)   ← control plane
├── Setup & Diagnostics panel
│     ├── Step-by-step diagnostic (workspace → config → health → tool call)
│     ├── MCP CRUD test  (create → get → comment → close → search)
│     ├── Connection switcher  (stdio / HTTP / Podman / Code Engine)
│     └── Send-to-chat prompts  (fire lesson scenarios into the active LLM)
├── Resources tree  (server status, connection mode, quick links)
└── Status-bar quick menu

MCP Server  (npm: mcp-ticket-demo)              ← what the extension manages
├── 10 tools · 3 resources · 5 prompts
├── Auth  — off / write / all · API key management · per-tool gates
├── stdio transport  — local child process, no port, secrets in env
├── HTTP transport   — POST /mcp  GET /sse  /health  /test  /admin  /tools
└── Podman image     — same Dockerfile as Code Engine deployment

Architecture

 ┌─────────────────────────────────────────────────────┐
 │  VS Code / Bob / Cursor / Windsurf                  │
 │                                                     │
 │  ┌──────────────────────┐   ┌─────────────────────┐ │
 │  │  MCP Platform Demo   │   │   LLM Chat Panel    │ │
 │  │  extension           │──▶│  (Copilot / Bob /   │ │
 │  │                      │   │   Cursor / Cascade) │ │
 │  │  sidebar · tree      │   └─────────────────────┘ │
 │  │  diagnose · prompts  │                           │
 │  └──────────┬───────────┘                           │
 └─────────────┼───────────────────────────────────────┘
               │  MCP  (stdio or HTTP)
   ┌───────────▼───────────────────────────────────┐
   │  mcp-ticket-demo  server                      │
   │                                               │
   │  Transport A — stdio                          │
   │    node  src/index.js  (child process)        │
   │    no port · no URL · secrets in env          │
   │                                               │
   │  Transport B — HTTP                           │
   │    POST /mcp  (Streamable HTTP)               │
   │    GET  /sse  (legacy SSE)                    │
   │    GET  /health  /test  /admin  /tools        │
   │                                               │
   │  Transport C — Podman HTTP                    │
   │    same image as Code Engine                  │
   │    podman run -p 8787:8080 mcp-ticket-demo    │
   └───────────────────────────────────────────────┘

Quick start

Option 1 — npm (no repo clone needed)

The MCP server is published at npmjs.com/package/mcp-ticket-demo.

npm install -g mcp-ticket-demo   # install globally
# or just run it directly
npx mcp-ticket-demo

Then install this extension. Open any folder and run MCP Demo: Connect native stdio from the command palette.

Option 2 — Clone the repo

git clone https://github.com/markusvankempen/mcp-ticket-demo
cd mcp-ticket-demo/server && npm install

Open the repo root in VS Code / Bob. The extension auto-detects server/src/index.js and all connection commands use it directly.


mcp.json examples

The extension writes these automatically via MCP Demo: Connect native stdio etc., but you can also paste them manually.

VS Code — .vscode/mcp.json

stdio (npx — no clone needed)

{
  "servers": {
    "mcp-ticket-demo": {
      "type": "stdio",
      "command": "npx",
      "args": ["mcp-ticket-demo"],
      "env": { "MCP_MODE": "stdio" }
    }
  }
}

stdio (local clone)

{
  "servers": {
    "mcp-ticket-demo": {
      "type": "stdio",
      "command": "node",
      "args": ["src/index.js"],
      "cwd": "/path/to/mcp-ticket-demo/server",
      "env": { "MCP_MODE": "stdio" }
    }
  }
}

Remote Code Engine (Streamable HTTP)

{
  "servers": {
    "mcp-ticket-demo-remote": {
      "type": "sse",
      "url": "https://<your-host>.codeengine.appdomain.cloud/sse"
    }
  }
}

IBM Bob — .bob/mcp.json

stdio (npx)

{
  "mcpServers": {
    "mcp-ticket-demo": {
      "command": "npx",
      "args": ["mcp-ticket-demo"],
      "env": { "MCP_MODE": "stdio" },
      "alwaysAllow": [
        "describe_server", "search_tickets", "create_ticket",
        "add_comment", "close_ticket", "get_ticket", "list_schemas",
        "get_schema", "run_query", "lookup_customer"
      ],
      "disabled": false
    }
  }
}

Remote Code Engine (Streamable HTTP)

{
  "mcpServers": {
    "mcp-ticket-demo-remote": {
      "type": "streamable-http",
      "url": "https://<your-host>.codeengine.appdomain.cloud/mcp",
      "alwaysAllow": [
        "describe_server", "search_tickets", "create_ticket",
        "add_comment", "close_ticket", "get_ticket", "list_schemas",
        "get_schema", "run_query", "lookup_customer"
      ],
      "disabled": false
    }
  }
}

Cursor — .cursor/mcp.json

stdio (npx)

{
  "mcpServers": {
    "mcp-ticket-demo": {
      "command": "npx",
      "args": ["mcp-ticket-demo"],
      "env": { "MCP_MODE": "stdio" }
    }
  }
}

Remote Code Engine (via uvx mcp-proxy shim)

{
  "mcpServers": {
    "mcp-ticket-demo-remote": {
      "command": "uvx",
      "args": ["mcp-proxy", "https://<your-host>.codeengine.appdomain.cloud/sse"]
    }
  }
}

Windsurf — .windsurf/mcp.json

stdio (npx)

{
  "mcpServers": {
    "mcp-ticket-demo": {
      "command": "npx",
      "args": ["mcp-ticket-demo"],
      "env": { "MCP_MODE": "stdio" }
    }
  }
}

Remote Code Engine (Streamable HTTP)

{
  "mcpServers": {
    "mcp-ticket-demo-remote": {
      "type": "streamable-http",
      "url": "https://<your-host>.codeengine.appdomain.cloud/mcp"
    }
  }
}

Cline — .vscode/settings.json

{
  "cline.mcpServers": {
    "mcp-ticket-demo": {
      "command": "npx",
      "args": ["mcp-ticket-demo"],
      "env": { "MCP_MODE": "stdio" },
      "alwaysAllow": [
        "describe_server", "search_tickets", "create_ticket",
        "add_comment", "close_ticket", "get_ticket", "list_schemas",
        "get_schema", "run_query", "lookup_customer"
      ],
      "disabled": false
    }
  }
}

With API key (auth mode: write or all)

Add to any env block for stdio, or add a headers block for HTTP:

stdio

"env": {
  "MCP_MODE": "stdio",
  "MCP_API_KEY": "your-api-key-from-admin"
}

HTTP (Bob / Windsurf streamable-http)

"headers": {
  "Authorization": "Bearer your-api-key-from-admin"
}

Issue API keys on /admin → API Keys. Set the auth mode on /admin → Auth Mode or via the summitMcp.authMode setting.


Connection modes

┌──────────────────────┬───────────────────────────────────────────────────┐
│ Mode                 │ How it works                                      │
├──────────────────────┼───────────────────────────────────────────────────┤
│ Native stdio         │ node src/index.js  spawned as a child process.    │
│                      │ No port. No URL. Credentials go in process env.   │
│                      │ Fastest for local dev.                            │
├──────────────────────┼───────────────────────────────────────────────────┤
│ Native HTTP          │ node src/index.js  runs an Express server on      │
│                      │ 127.0.0.1:8787. Use /health /test /admin /mcp.    │
├──────────────────────┼───────────────────────────────────────────────────┤
│ Podman stdio         │ podman run -i --rm  spawns the container as a     │
│                      │ stdio child. Same MCP_MODE=stdio path.            │
├──────────────────────┼───────────────────────────────────────────────────┤
│ Podman HTTP          │ Container maps port 8787 → 8080.                  │
│                      │ IDE connects to http://127.0.0.1:8787/mcp.        │
├──────────────────────┼───────────────────────────────────────────────────┤
│ Remote Code Engine   │ Public HTTPS endpoint on IBM Cloud.               │
│                      │ IDE connects to https://<host>/mcp or /sse.       │
└──────────────────────┴───────────────────────────────────────────────────┘

MCP Tools (10 total)

┌──────────────────┬───────────┬────────────────────────────────────────────┐
│ Tool             │ Scope     │ Purpose                                    │
├──────────────────┼───────────┼────────────────────────────────────────────┤
│ describe_server  │ read      │ Identity, auth mode, your scopes, rate     │
│                  │           │ limit, and the full tool inventory.        │
│                  │           │ Call this first when anything is denied.   │
├──────────────────┼───────────┼────────────────────────────────────────────┤
│ search_tickets   │ read      │ Find tickets by status / requester /       │
│                  │           │ keyword. Returns empty + hint on no match. │
├──────────────────┼───────────┼────────────────────────────────────────────┤
│ get_ticket       │ read      │ Fetch one ticket by id, with comments      │
│                  │           │ and attribution.                           │
├──────────────────┼───────────┼────────────────────────────────────────────┤
│ create_ticket    │ write     │ Open a ticket. ALWAYS pass requester_email │
│                  │           │ or the service account owns it and every   │
│                  │           │ reply goes to the bot.                     │
├──────────────────┼───────────┼────────────────────────────────────────────┤
│ add_comment      │ write     │ Comment on a known ticket id.              │
├──────────────────┼───────────┼────────────────────────────────────────────┤
│ close_ticket     │ write     │ Resolve a ticket. Optional resolution note │
│                  │           │ becomes the last comment.                  │
├──────────────────┼───────────┼────────────────────────────────────────────┤
│ list_schemas     │ read      │ Discover queryable schemas before calling  │
│                  │           │ run_query. Replaces query_tickets et al.   │
├──────────────────┼───────────┼────────────────────────────────────────────┤
│ get_schema       │ read      │ Fields and filterable keys for one schema. │
├──────────────────┼───────────┼────────────────────────────────────────────┤
│ run_query        │ read      │ The one query tool. Pass schema= from      │
│                  │           │ list_schemas. Do not invent query_tickets. │
├──────────────────┼───────────┼────────────────────────────────────────────┤
│ lookup_customer  │ pii       │ Customer record. Phone is PII — redacted   │
│                  │           │ unless caller holds the pii scope.         │
└──────────────────┴───────────┴────────────────────────────────────────────┘

Queryable schemas

tickets    id · subject · status · requester_email · attribution · created_at
           filterable: status · requester_email · attribution

customers  email · name · plan · region
           filterable: email · region

assets     id · name · site · status
           filterable: status · site

Auth model

┌──────────┬──────────────────────────────────────────────────────────┐
│ Mode     │ Effect                                                   │
├──────────┼──────────────────────────────────────────────────────────┤
│ off      │ All tools callable. No credential needed. Default.       │
├──────────┼──────────────────────────────────────────────────────────┤
│ write    │ read tools open. write + pii tools need a credential.    │
├──────────┼──────────────────────────────────────────────────────────┤
│ all      │ Every tool call requires a credential.                   │
└──────────┴──────────────────────────────────────────────────────────┘

Accepted credentials
  HTTP  →  Authorization: Bearer <api key>
           Authorization: Basic base64(user:pass)
           x-api-key: <api key>

  stdio →  MCP_API_KEY=<key>  in server process env
           MCP_USERNAME + MCP_PASSWORD  in server process env

Issue API keys on  /admin → API Keys.
Switch mode from   /admin → Auth Mode  or summitMcp.authMode setting.

Send-to-chat prompts

The sidebar and panel include five one-click prompts that fire directly into the active LLM chat (VS Code Copilot, IBM Bob, Cursor, Windsurf Cascade). Clipboard fallback is automatic when the chat API is not exposed.

1. Search open tickets
   "Search open tickets, then tell me who owns each one."

2. create_ticket without requester  ← the attribution-scar lesson
   "Create a ticket … do not pass requester_email.
    Then tell me who owns it."

3. create_ticket with requester
   "Create a ticket for ada@example.com …
    Then search tickets owned by ada@example.com."

4. list_schemas → run_query  ← the schema-discovery lesson
   "List schemas, get the tickets schema,
    then run_query on tickets filtered to status=open."

5. 0 tools discovered  ← the silent-failure lesson
   "If tools/list is empty, tell me why — cwd, MCP_MODE,
    native stdio vs Podman vs Code Engine — and what to try."

Chat delivery chain

prompt text
    │
    ├─▶ 1. workbench.action.chat.open  { query, submit }   VS Code Copilot
    ├─▶ 2. inlineChat.start            { query }           VS Code inline
    ├─▶ 3. Bob.focus → paste → Bob.acceptInput             IBM Bob
    ├─▶ 4. windsurf.openCascade        { query }           Windsurf
    ├─▶ 5. aichat.newchataction        { query }           Cursor AI Chat
    ├─▶ 6. composer.startComposer      { query }           Cursor Composer
    ├─▶ 7. workbench.action.chat.newChat → paste → submit  Generic fallback
    └─▶ 8. clipboard + info message                        Last resort

Diagnostics

Run MCP Demo: Diagnose to execute a full health sweep:

Step                    What is checked
──────────────────────────────────────────────────────────────────
Workspace               Folder is open
Server package          server/src/index.js exists
Server dependencies     server/node_modules installed
Native stdio config     .vscode / .cursor / .bob / .windsurf written
Client MCP config       At least one mcp.json present
Podman MCP config       Podman entry present (when probe=podman)
Local Podman            podman version, container status
GET /health             HTTP 200, version, tool count (`cwd` on localhost only)
GET /test               Public read-only smoke passed
tools/list              At least 1 tool returned (the silent failure)
search_tickets          Real call returns TCK- ids

MCP Demo: Run MCP CRUD test (panel → MCP Test tab) creates a ticket, reads it, comments, closes it, then searches with status=all. It scores the tool JSON (ok, ticket.id, status), not HTTP 200.


Commands

LF MCP Demo: Discover mcp.json          Scan workspace for existing configs
LF MCP Demo: Connect native stdio        Write node entry to all client configs
LF MCP Demo: Connect remote Code Engine  Prompt for URL, write SSE/HTTP entry
LF MCP Demo: Connect Podman stdio        Write podman run -i entry
LF MCP Demo: Connect Podman HTTP         Write podman HTTP entry
LF MCP Demo: Build & start local Podman  podman build + podman run in one step
LF MCP Demo: Stop local Podman           Stop the running container
LF MCP Demo: Start native HTTP server    Open terminal, run MCP_MODE=http
LF MCP Demo: Diagnose                    Full health sweep + panel
LF MCP Demo: Open diagnostics panel      Panel without running diagnostics
LF MCP Demo: Send prompt to chat         Prompt picker → fire into LLM chat
LF MCP Demo: Chat — search open tickets  One-click canned prompt
LF MCP Demo: Save settings               Persist sidebar form values
LF MCP Demo: Open /health                Open in browser
LF MCP Demo: Open /test                  Open in browser
LF MCP Demo: Open /admin                 Open in browser
LF MCP Demo: Open /tools                 Open in browser
LF MCP Demo: Open /help                  Open in browser
LF MCP Demo: Open mcp.json               Show the written config file
LF MCP Demo: Show quick menu             Status-bar shortcut menu
LF MCP Demo: Register server with all IDEs  Write mcp.json for VS Code, Cursor, Bob, Windsurf
LF MCP Demo: Run MCP CRUD test           create → get → comment → close → search (status=all)
LF MCP Demo: Install bundled server      npm install mcp-ticket-demo into global storage
LF MCP Demo: Update server from npm      Check npm for newer version and install it
Refresh                                  Reload the Resources tree

Settings

summitMcp.probeTarget      auto | native-http | podman | remote
                           Which surface /health and Diagnose probe.

summitMcp.remoteUrl        Public Code Engine URL (no trailing slash).

summitMcp.localHttpUrl     Local HTTP URL. Default: http://127.0.0.1:8787

summitMcp.podmanImage      Image name. Default: mcp-ticket-demo:local

summitMcp.podmanContainer  Container name. Default: mcp-ticket-demo

summitMcp.podmanPort       Host port mapped to container 8080. Default: 8787

summitMcp.adminUser        /admin username. Default: demo

summitMcp.adminPassword    /admin password. Change before sharing.

summitMcp.demoToken        Legacy bearer token. Prefer an API key.

summitMcp.apiKey           API key issued on /admin → API Keys.
                           Sent as Authorization: Bearer over HTTP;
                           as MCP_API_KEY to stdio / Podman processes.

summitMcp.autoConnectStdio true (default)
                           Write native stdio into mcp.json on activate
                           if missing. Never overwrites HTTP/SSE.

summitMcp.authMode         off | write | all
                           Auth mode the local server boots with.

HTTP endpoints (native HTTP and Podman)

GET  /health         Liveness. Version, tool count. `cwd` only on localhost.
GET  /test           Read-only smoke. `/test?write=1` after admin sign-in creates + closes.
GET  /admin          Admin UI — auth mode, API key management, audit log.
GET  /tools          Tool inventory page.
GET  /help           Setup instructions.
POST /mcp            Streamable HTTP MCP transport (JSON-RPC 2.0).
GET  /sse            Legacy SSE MCP transport.

Client compatibility

┌─────────────────┬────────┬──────────┬────────────┬─────────┐
│                 │ stdio  │ HTTP/SSE │ Streamable │ Podman  │
│                 │        │          │ HTTP       │ stdio   │
├─────────────────┼────────┼──────────┼────────────┼─────────┤
│ VS Code Copilot │  yes   │  SSE     │    yes     │  yes    │
│ IBM Bob         │  yes   │  —       │    yes     │  yes    │
│ Cursor          │  yes   │  uvx     │    —       │  yes    │
│ Windsurf        │  yes   │  —       │    yes     │  yes    │
│ Cline           │  yes   │  SSE     │    yes     │  yes    │
└─────────────────┴────────┴──────────┴────────────┴─────────┘

Cursor does not support Streamable HTTP natively — the extension writes a uvx mcp-proxy <sse-url> stdio shim automatically.


MCP server — standalone use

The server is published separately on npm as mcp-ticket-demo.

# stdio (default)
npx mcp-ticket-demo

# HTTP
MCP_MODE=http npx mcp-ticket-demo

# With auth
AUTH_MODE=write MCP_API_KEY=mykey MCP_MODE=http npx mcp-ticket-demo

Talk lessons this demo is built around

Lesson 1 — The silent tool-count failure
  tools/list returns 0 tools with no error when the server starts
  but the cwd is wrong or MCP_MODE is unset. The diagnostic step
  reproduces this exactly.

Lesson 2 — Attribution scar
  create_ticket without requester_email returns HTTP 201.
  The ticket exists. The API call "succeeded". But the service
  account owns it and every reply goes to the bot, not the customer.
  The create_ticket prompt demonstrates this in one LLM turn.

Lesson 3 — Schema discovery over tool proliferation
  list_schemas → get_schema → run_query replaces a pile of
  query_tickets / query_assets / query_with_filter tools.
  The model discovers the shape; the server exposes one tool.

Lesson 4 — Laptop paths don't survive a container boundary
  Native stdio uses cwd: serverDir() (absolute, local).
  Podman stdio uses the image — no absolute path.
  Code Engine uses the image — no local filesystem.
  The "mcp.json worked yesterday, 404 today" seed ticket (TCK-1003)
  illustrates the hostname rotation problem on managed platforms.

Author: Markus van Kempen · markus.van.kempen@gmail.com · markusvankempen.github.io · MCP Dev Summit Talk · Talk slides

Personal open-source demo. Not an IBM product.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft