Skip to main content
Glama
shanchiehchiu

chatgpt-apps-sdk-erp-demo

ChatGPT Apps SDK + MCP ERP Demo

這是一個給團隊理解 MCP、MCP Apps 與 ChatGPT Apps SDK 的最小可跑範例。

它模擬一個常見 ERP 流程:

  1. 使用者在 ChatGPT 詢問「有哪些訂購明細還沒轉生產工單」

  2. ChatGPT 呼叫 MCP Tool

  3. MCP Server 取得 ERP 資料

  4. ChatGPT 內直接顯示互動式 Widget

  5. 使用者勾選資料後,在 Widget 內再呼叫 MCP Tool

  6. 右側預覽 BOM、半成品與生產工單結構

這個 Repository 是公開教學版。所有客戶、產品、BOM、品牌、專案名稱與資料都已改成 Mock Data,沒有連線任何真實 ERP 或資料庫。


先用一句話理解

如果同事不知道 MCP,可以先這樣理解:

MCP 可以先想成「給 AI 使用的 API 標準」;Apps SDK 則讓 MCP Tool 不只回文字,還能在 ChatGPT 裡顯示可操作的 UI。

使用者
  ↓
ChatGPT
  ↓  理解自然語言
MCP Tool
  ↓
你的後端 / ERP / API
  ↓
Tool Result
  ↓
MCP Apps UI Resource
  ↓
ChatGPT 裡的 React Widget

Related MCP server: Todo MCP Server

名詞不用背很多

名詞

在這個 Demo 裡的用途

MCP Tool

AI 可以呼叫的後端能力

MCP Resource

MCP 可以讀取的資源

MCP Apps

MCP 的 UI 擴充標準,讓 Tool 可以綁定互動式 UI

Apps SDK

ChatGPT 對 MCP / MCP Apps 的產品整合與 UI 能力

Widget

真正在 ChatGPT iframe 裡執行的前端 UI

最重要的觀念:

MCP Tool
= 做事情

UI Resource
= 畫介面

_meta.ui.resourceUri
= 把 Tool 和 UI 綁在一起

這個 Demo 做了什麼

1. MCP Server

server.js 提供八個 Tool:

  • get_demo_status

  • run_round_trip

  • get_production_candidates

  • get_customer_sales_ranking

  • get_data_grid_demo

  • get_form_demo

  • submit_form_demo

  • preview_production_orders

其中包含兩個 ERP UI use case,以及獨立的 data-grid / form Primitive Lab。

2. Model / Domain Data

server/model/demo-erp.js 把 Mock ERP 整理成穩定的 canonical data:

{
  domain: "production_candidates",
  total: 5,
  records: [...]
}

這一層只知道 ERP 資料,不知道畫面長什麼樣。

3. Presentation / ViewModel

server/presentation/ 只描述「資料要怎麼呈現」;目前包含 production、sales、grid 與 form 四組 ViewModel:

{
  renderer: "collection-workspace",
  search: {...},
  collection: {...},
  selection: {...}
}

資料與表示分開,所以未來換 Renderer 或接 Host-native Gen UI,不需要重寫 ERP capability。

4. MCP UI Resource

Server 註冊:

ui://widget/erp-production-demo-v7.html

Widget build 後會以 HTML resource 提供給支援 MCP Apps 的 Host。

5. Generic UI Runtime

src/runtime/ 不是某一個 ERP 功能的完整頁面,而是一組可重複使用的 Renderer。

目前有:

  • collection-workspace:inline 摘要、搜尋、多選、批次 action、fullscreen 工作台

  • data-grid:欄位 schema、搜尋、篩選、排序、分頁、checkbox、多選 action、badge / number / currency / date formatting

  • form:section / field schema、預設值、readonly / disabled、條件顯示、條件必填、前端驗證、MCP submit、後端欄位錯誤回填、儲存狀態與 idempotency

  • ranked-list:排行、摘要指標、占比、inline / fullscreen

  • tree-detail:樹狀工單 / BOM / 用料 detail

data-grid 與 form 都刻意用獨立 Primitive Lab 驗證,不新增新的 ERP 業務頁面。表單保持 label 永久可見、優先使用預設值與既有資料,錯誤直接顯示在欄位旁;submit 會帶 submission id 做 idempotency,成功後呈現「已儲存」與時間,避免重複送出造成重複寫入。

第二個 use case「客戶銷售排行」已經用 ranked-list 完成,而且沒有建立 CustomerSalesRankingPage.jsx。金額格式也由 domain data 的幣別資訊轉成 presentation schema,例如 NTD 會顯示為 NT$;Renderer 本身不寫死幣別。這就是資料 / Presentation / Renderer 分離真正要解決的問題。

6. Mock ERP

mock-erp.js 完全是假的範例資料:

  • 假客戶

  • 假訂單

  • 假產品

  • 假 BOM

  • 假工單編號

所以 Clone 下來就能直接跑,不需要公司資料庫。


實際資料流

第一次由模型呼叫:

使用者
  ↓
ChatGPT
  ↓
MCP Tool / Controller
  ↓
Model → Canonical Domain Data
  +
Presentation Adapter → UI Schema
  ↓
_meta.erpUi
  ↓
Generic AppRenderer
  ↓
collection-workspace

使用者在 Widget 裡勾選後:

Generic Renderer
  ↓ tools/call
preview_production_orders
  ↓
Model → Preview Domain Data
  +
Presentation → tree-detail schema
  ↓
同一個 UI Runtime
  ↓
右側展開 BOM / 半成品 / 用料

這就是 Apps SDK / MCP Apps 比純文字 Tool 多出來的價值:

同一個對話裡,可以從自然語言進入 UI,再從 UI 繼續操作後端。


專案結構

.
├── server.js                  # MCP Controller / Tools / UI Resource
├── mock-erp.js                # 完全假的 ERP 原始資料
├── server/
│   ├── demo/
│   │   ├── form-fixture.js    # Form Primitive 的 Mock Data / 驗證
│   │   └── grid-fixture.js    # Data Grid Primitive 的 Mock Data
│   ├── model/
│   │   └── demo-erp.js        # Canonical Domain Data
│   └── presentation/
│       ├── form.js            # Form ViewModel Schema
│       ├── grid.js            # Data Grid ViewModel Schema
│       ├── production.js      # 生產流程 ViewModel
│       ├── sales.js           # 銷售排行 ViewModel
│       └── ui.js              # 共用 ViewModel helper
├── src/
│   ├── main.jsx               # 很薄的 App entry
│   ├── mcp/
│   │   └── useMcpBridge.js    # MCP Apps bridge
│   ├── runtime/
│   │   ├── AppRenderer.jsx
│   │   ├── BrandLockup.jsx
│   │   ├── CollectionWorkspace.jsx
│   │   ├── DataGrid.jsx
│   │   ├── FormRenderer.jsx
│   │   ├── RankedList.jsx
│   │   ├── TreeDetail.jsx
│   │   └── value.js
│   ├── components/
│   │   └── ProjectStatusCard.jsx
│   └── main.css
├── smoke-test.js
├── dev-tunnel.js
└── package.json

更完整的分層說明請看 ARCHITECTURE.md。


本機啟動

需求:

  • Node.js 20+

  • npm

安裝:

npm install

先跑整合測試:

npm run test:smoke

正常會看到:

SMOKE OK

啟動 MCP Server:

npm run start

預設:

http://localhost:18787/mcp

要接 ChatGPT 測試

ChatGPT 需要可以從網路存取你的 MCP Server,所以本機開發時需要 HTTPS tunnel。

如果有安裝 cloudflared:

npm run dev:tunnel

它會輸出類似:

https://xxxxx.trycloudflare.com/mcp

然後在 ChatGPT Developer Mode 裡新增 MCP / Plugin 連線,填入這個 /mcp URL。

開發用 Quick Tunnel 不適合正式環境。正式部署請使用自己的 HTTPS domain、Authentication、Authorization 與 audit log。


Tool 為什麼不直接回 HTML?

這裡刻意把兩件事分開:

Tool 回資料

return {
  structuredContent: {
    view: "erp_ui",
    domain: data.domain,
    count: data.total
  },
  _meta: {
    erpUi: {
      slot: presentation.slot,
      presentation,
      data
    }
  }
};

Resource 回 UI

registerAppResource(
  server,
  "erp-production-demo-widget",
  "ui://widget/erp-production-demo-v7.html",
  ...
);

再用 metadata 綁定

_meta: {
  ui: {
    resourceUri: "ui://widget/erp-production-demo-v7.html"
  }
}

所以不是 Tool 直接塞一坨 HTML 回去。

比較接近:

Tool
≈ Controller / API

UI Resource
≈ View / SPA Bundle

resourceUri
≈ 指定這個 Tool 要搭配哪個 View

structuredContent 和 _meta 的差別

這個 Demo 刻意示範一個 UI-first 做法。

structuredContent 只放很小的 view marker:

{
  view: "production_candidates"
}

完整列表放在:

_meta.erpCandidates

好處是可以降低模型把 Widget 裡的資料再重複講一次的機率。

但如果你的 Tool 必須在「不支援 UI 的 MCP Client」也完整可用,就應該把模型需要的安全資料放進 structuredContent。

也就是:

_meta 適合 UI 專用資料;structuredContent 適合模型也需要理解的資料。


如何換成真實 ERP

Demo 現在是:

server.js
  ↓
mock-erp.js

正式專案可以替換成:

server.js
  ↓
REST API
  ↓
Laravel / Rails / Spring / Rust
  ↓
DB

或:

server.js
  ↓
內部 service / adapter
  ↓
既有 ERP business logic

建議原則:

  1. 不要把 business logic 重寫進 MCP Server

  2. MCP Server 只負責把既有能力包成清楚的 Tool

  3. Tool input / output 要有 schema

  4. 寫入型 Tool 要清楚標示 destructive / confirmation 行為

  5. UI 只是 workflow layer,不要讓安全規則只存在前端


為什麼有 inline 和 fullscreen?

Inline 適合:

  • 快速看摘要

  • 顯示少量待處理資料

  • 提供進入完整工作流程的入口

Fullscreen 適合:

  • 搜尋

  • 多選

  • 批次操作

  • 複雜資料比較

  • BOM / tree / split view

不要把整套 ERP 塞進聊天訊息裡的小卡片。


安全注意事項

公開 Repository 前至少確認:

  • 不要提交 API Key / Token / Password

  • 不要提交真實客戶名稱

  • 不要提交真實產品與訂單

  • 不要提交內網 Domain / IP

  • 不要提交本機絕對路徑

  • 不要提交 production DB connection

  • 不要把 Authorization 只做在 Widget

  • 不要因為 Tool 是 app-only 就假設後端不需要權限檢查

這個 Demo 本身沒有任何真實系統連線。


技術棧

  • React

  • Vite

  • Tailwind CSS

  • @modelcontextprotocol/sdk

  • @modelcontextprotocol/ext-apps

  • @openai/apps-sdk-ui

  • Zod


延伸閱讀


License

MIT

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    Not graded
    maintenance
    An MCP server that enables LLM agents to interact with PyerP ERP systems via a REST API. It allows users to search, read, create, and update ERP records such as inventory, clients, and users using natural language.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    A minimal todo app MCP server for ChatGPT that exposes tools for managing tasks and provides an interactive HTML widget interface. It demonstrates how to build MCP servers with React-based UIs and includes development OAuth for ChatGPT connector setup.
    -