📋 概述

擴充功能系統讓外部程序訂閱 Aspose MCP Server 的 Session 變更事件。當 Session 中的文件被修改時,系統會自動將文件快照發送給已綁定的擴充功能程序,開發者可據此實現各種自訂應用。

系統架構

伺服器啟動 → 載入擴展配置 → MCP 就緒
                 ↓
         背景初始化(異步)
                 ↓
         啟動所有擴展程序 → 握手交換元資料
                                    ↓
MCP Client → extension(bind) → ExtensionSessionBridge ←─┘
                                    ↓
Session 變更 → DocumentConversionService → 快照
                                    ↓
                    Extension Process (Node.js/Python/etc.)
                                    ↓
                         接收快照 → 處理 → 回應 ACK

生命週期

💡 適用場景

以下是開發者可透過擴充功能系統實現的應用方向:

預覽與呈現

安全與合規

協作與整合

智慧處理

自訂處理

⚙️ 啟用擴充功能系統

擴充功能系統需要同時啟用 Session 功能。使用以下參數啟用:

快速啟動

# 環境變數
ASPOSE_SESSION_ENABLED=true
ASPOSE_EXTENSION_ENABLED=true
ASPOSE_EXTENSION_CONFIG=/path/to/extensions.json

# 或命令行參數
./AsposeMcpServer --http \
    --session-enabled \
    --extension-enabled \
    --extension-config ./extensions.json
📖 完整配置參考

完整的擴充功能參數說明請參考 配置參考 - 擴充功能設定 ,包含所有系統級設定和可覆寫設定(含 Floor/Ceiling 約束)。

📄 擴展配置檔案

擴展配置檔案(extensions.json)定義所有已註冊的擴展。使用物件格式,key 作為擴展的唯一 ID。

完整範例

{
  "extensions": {
    "pdf-viewer": {
      "command": {
        "type": "node",
        "executable": "/path/to/index.js",
        "arguments": "--port 8080",
        "workingDirectory": "/path/to/extension",
        "environment": {
          "NODE_ENV": "production"
        }
      },
      "inputFormats": ["pdf"],
      "supportedDocumentTypes": ["word", "excel", "powerpoint", "pdf"],
      "transportModes": ["mmap", "stdin", "file"],
      "preferredTransportMode": "file",
      "capabilities": {
        "supportsHeartbeat": true,
        "frameIntervalMs": 100
      }
    },
    "cloud-sync": {
      "command": {
        "type": "python",
        "executable": "/path/to/main.py"
      },
      "inputFormats": ["pdf", "html"],
      "supportedDocumentTypes": ["word", "excel"],
      "transportModes": ["file"],
      "preferredTransportMode": "file",
      "capabilities": {
        "supportsHeartbeat": true,
        "idleTimeoutMinutes": 5
      }
    }
  }
}
💡 配置格式說明

配置檔使用物件格式(而非陣列),key 即為擴展的唯一 ID(不可包含冒號)。 擴展的名稱、版本等元資料透過握手協議動態交換,無需在配置檔中指定。

欄位說明

欄位 說明
extensions.{id} 物件的 key 作為擴展唯一識別符(不可包含冒號)
command.type 命令類型:executable / node / python / dotnet / npx / pipx / custom
command.executable 可執行檔、腳本路徑或套件名稱(npx/pipx 時為套件名稱)
command.arguments 命令列參數
command.workingDirectory 工作目錄
command.environment 環境變數(支援 ${VAR} 引用系統環境變數)
inputFormats 接受的輸入格式陣列:pdf / html / png
supportedDocumentTypes 支援的文檔類型:word / excel / powerpoint / pdf
transportModes 支援的傳輸模式陣列:mmap / stdin / file
preferredTransportMode 偏好的傳輸模式
capabilities.supportsHeartbeat 是否支援心跳檢測
capabilities.frameIntervalMs 快照傳輸間隔毫秒數(預設:100,範圍:10-5000)
capabilities.snapshotTtlSeconds 未確認快照的 TTL 秒數(預設:30,範圍:5-300)
capabilities.maxMissedHeartbeats 最大連續心跳失敗次數(預設:3,範圍:1-20)
capabilities.idleTimeoutMinutes 閒置卸載超時分鐘數(預設:30,範圍:1-1440,0=常駐模式)

🤝 握手協議

所有擴展在服務啟動後會在背景異步啟動,並執行初始化握手。透過握手,擴展會回報自己的名稱、版本等元資料。握手期間擴展處於 Initializing 狀態,此時無法進行綁定操作。

握手流程

MCP Server                                    Extension
    │                                              │
    │  1. 啟動擴展程序                              │
    │─────────────────────────────────────────────>│
    │                                              │
    │  2. {"type":"initialize","protocolVersion":"1.0"}
    │─────────────────────────────────────────────>│
    │                                              │
    │  3. {"type":"initialize_response","name":"...","version":"..."}
    │<─────────────────────────────────────────────│
    │                                              │
    │  4. {"type":"initialized"}                   │
    │─────────────────────────────────────────────>│
    │                                              │
    │  握手完成,開始正常運作                        │

握手消息

initialize(MCP Server → Extension)

伺服器發送給擴展,啟動握手流程。

{
  "type": "initialize",
  "protocolVersion": "1.0"
}

initialize_response(Extension → MCP Server)

擴展回應其元資料。nameversion 為必填欄位。

{
  "type": "initialize_response",
  "name": "PDF Viewer",
  "version": "1.0.0",
  "title": "PDF 即時預覽器",
  "description": "提供 PDF 文件的即時預覽功能",
  "author": "開發者名稱",
  "websiteUrl": "https://example.com"
}
欄位 必填 說明
name 擴展顯示名稱
version 版本號(建議使用 semver 格式)
title 本地化標題(用於 UI 顯示)
description 擴展功能說明
author 作者資訊
websiteUrl 擴展網站或文檔 URL

initialized(MCP Server → Extension)

伺服器確認握手完成。收到此消息後,擴展可開始正常處理快照等消息。

{
  "type": "initialized"
}

握手失敗處理

🚚 傳輸模式

擴充功能系統支援三種傳輸模式,用於將快照資料發送給擴充功能程序:

mmap (Memory-Mapped File)

使用記憶體映射檔案傳輸資料。跨平台支援,效能最佳。

  • Windows:使用 Windows 內核命名共享記憶體
  • Linux:使用 POSIX shm_open (/dev/shm/)
  • macOS:使用檔案支持的記憶體映射(自動降級)
  • 優點:零拷貝(Windows/Linux),高效能
  • 適用場景:大型文件預覽,需要最佳效能時

stdin (Standard Input) - 預設

透過標準輸入發送二進位資料。跨平台相容,無磁碟 I/O 開銷。

  • 優點:跨平台、低延遲、無需暫存檔
  • 缺點:需要處理二進位串流
  • 適用場景:大多數場景(預設)

file (Temporary File)

將快照寫入臨時檔案,透過路徑傳遞給擴展。實作最簡單。

  • 優點:實作簡單,支援所有語言
  • 缺點:需要磁碟 I/O
  • 適用場景:簡單整合、除錯測試

📨 消息協議

擴展程序透過標準輸入輸出與 MCP Server 通訊。消息格式為 JSON,每行一條消息。

💡 握手消息

擴展啟動時必須先完成握手協議(initialize → initialize_response → initialized),才會收到以下消息。

MCP Server → 擴充功能 消息

snapshot

文件快照消息,包含轉換後的文件資料。

{
  "protocolVersion": "1.0",
  "type": "snapshot",
  "sessionId": "sess_abc123",
  "documentType": "word",
  "outputFormat": "pdf",
  "mimeType": "application/pdf",
  "transportMode": "file",
  "filePath": "/tmp/extensions/snapshot_xxx.pdf",
  "dataSize": 12345,
  "checksum": 1234567890,
  "sequenceNumber": 1,
  "timestamp": "2024-01-01T12:00:00Z"
}

heartbeat

心跳檢測消息,用於確認擴展存活。

{
  "protocolVersion": "1.0",
  "type": "heartbeat",
  "timestamp": "2024-01-01T12:00:00Z"
}

command

命令消息,由 MCP 客戶端發送給擴展。

{
  "protocolVersion": "1.0",
  "type": "command",
  "sessionId": "sess_abc123",
  "commandId": "cmd_abc123",
  "commandType": "highlight",
  "commandPayload": { "text": "important" },
  "timestamp": "2024-01-01T12:00:00Z"
}

session_closed

Session 關閉消息,通知擴展停止處理該 Session 並清理資源。

{
  "protocolVersion": "1.0",
  "type": "session_closed",
  "sessionId": "sess_abc123",
  "timestamp": "2024-01-01T12:00:00Z"
}

session_unbound

Session 解除綁定消息,通知擴展該 Session 不再綁定到此擴展(但 Session 仍存在)。

{
  "protocolVersion": "1.0",
  "type": "session_unbound",
  "sessionId": "sess_abc123",
  "timestamp": "2024-01-01T12:00:00Z"
}

擴充功能 → MCP Server 消息

ack

快照確認消息,表示已成功接收並處理快照。

{
  "type": "ack",
  "sessionId": "sess_abc123",
  "sequenceNumber": 1,
  "success": true
}

heartbeat_response

心跳回應消息。

{
  "type": "heartbeat_response",
  "status": "ok",
  "timestamp": "2024-01-01T12:00:00Z"
}

🔧 擴充功能工具操作

MCP 客戶端可透過 extension 工具管理擴展和綁定。

list - 列出所有擴展

{
  "operation": "list"
}

bind - 綁定 Session 到擴展

{
  "operation": "bind",
  "sessionId": "abc123",
  "extensionId": "pdf-viewer",
  "format": "pdf"
}

unbind - 解除綁定

{
  "operation": "unbind",
  "sessionId": "abc123",
  "extensionId": "pdf-viewer"
}

status - 查詢擴展狀態

{
  "operation": "status",
  "extensionId": "pdf-viewer"
}

set_format - 設定快照格式

{
  "operation": "set_format",
  "sessionId": "abc123",
  "extensionId": "pdf-viewer",
  "format": "html"
}

bindings - 列出 Session 綁定

{
  "operation": "bindings",
  "sessionId": "abc123"
}

command - 發送命令給擴展

{
  "operation": "command",
  "sessionId": "abc123",
  "extensionId": "pdf-viewer",
  "command": "goto",
  "data": { "page": 5 }
}

💻 開發範例

Node.js 擴展範例

const readline = require('readline');

let initialized = false;

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    terminal: false
});

rl.on('line', (line) => {
    try {
        const message = JSON.parse(line);
        const msgType = message.type;

        switch (msgType) {
            // ===== 握手協議 =====
            case 'initialize':
                // 回應握手,提供擴展元資料
                const initResponse = {
                    type: 'initialize_response',
                    name: 'My Extension',
                    version: '1.0.0',
                    title: '我的擴展功能',
                    description: '提供自訂文件處理功能',
                    author: '開發者名稱',
                    websiteUrl: 'https://example.com'
                };
                console.log(JSON.stringify(initResponse));
                console.error('Handshake: sent initialize_response');
                break;

            case 'initialized':
                // 握手完成,開始正常運作
                initialized = true;
                console.error('Handshake completed, ready to receive messages');
                break;

            // ===== 正常運作消息 =====
            case 'snapshot':
                if (!initialized) {
                    console.error('Warning: received snapshot before initialization');
                    return;
                }
                // 處理快照
                console.error(`Received snapshot for session ${message.sessionId}`);
                console.error(`File: ${message.filePath}, Size: ${message.dataSize}`);

                // 驗證 checksum(可選但建議)
                // const data = fs.readFileSync(message.filePath);
                // if (crc32(data) !== message.checksum) { /* 處理錯誤 */ }

                // 發送 ACK
                const ack = {
                    type: 'ack',
                    sessionId: message.sessionId,
                    sequenceNumber: message.sequenceNumber,
                    success: true
                };
                console.log(JSON.stringify(ack));
                break;

            case 'heartbeat':
                // 回應心跳
                const response = {
                    type: 'heartbeat_response',
                    status: 'ok',
                    timestamp: new Date().toISOString()
                };
                console.log(JSON.stringify(response));
                break;

            case 'command':
                // 處理命令
                console.error(`Received command: ${message.commandType}`);
                console.error(`Payload: ${JSON.stringify(message.commandPayload)}`);
                break;

            case 'session_closed':
                // 清理 Session 資源
                console.error(`Session ${message.sessionId} closed`);
                break;

            case 'session_unbound':
                // Session 解除綁定(Session 仍存在,但不再綁定到此擴展)
                console.error(`Session ${message.sessionId} unbound`);
                break;

            case 'shutdown':
                // 準備關閉
                console.error('Shutdown requested');
                process.exit(0);
                break;
        }
    } catch (error) {
        console.error('Error processing message:', error);
    }
});

console.error('Extension started, waiting for handshake...');

Python 擴展範例

import sys
import json
from datetime import datetime

initialized = False

def main():
    global initialized
    print("Extension started, waiting for handshake...", file=sys.stderr)

    for line in sys.stdin:
        try:
            message = json.loads(line.strip())
            msg_type = message.get('type')

            # ===== 握手協議 =====
            if msg_type == 'initialize':
                # 回應握手,提供擴展元資料
                init_response = {
                    'type': 'initialize_response',
                    'name': 'My Extension',
                    'version': '1.0.0',
                    'title': '我的擴展功能',
                    'description': '提供自訂文件處理功能',
                    'author': '開發者名稱',
                    'websiteUrl': 'https://example.com'
                }
                print(json.dumps(init_response))
                sys.stdout.flush()
                print("Handshake: sent initialize_response", file=sys.stderr)

            elif msg_type == 'initialized':
                # 握手完成,開始正常運作
                initialized = True
                print("Handshake completed, ready to receive messages", file=sys.stderr)

            # ===== 正常運作消息 =====
            elif msg_type == 'snapshot':
                if not initialized:
                    print("Warning: received snapshot before initialization", file=sys.stderr)
                    continue
                # 處理快照
                session_id = message['sessionId']
                file_path = message['filePath']
                data_size = message['dataSize']
                seq_num = message['sequenceNumber']
                print(f"Received snapshot: {file_path} ({data_size} bytes)", file=sys.stderr)

                # 驗證 checksum(可選但建議)
                # checksum = message['checksum']
                # with open(file_path, 'rb') as f:
                #     if crc32(f.read()) != checksum: handle_error()

                # 發送 ACK
                ack = {
                    'type': 'ack',
                    'sessionId': session_id,
                    'sequenceNumber': seq_num,
                    'success': True
                }
                print(json.dumps(ack))
                sys.stdout.flush()

            elif msg_type == 'heartbeat':
                # 回應心跳
                response = {
                    'type': 'heartbeat_response',
                    'status': 'ok',
                    'timestamp': datetime.utcnow().isoformat() + 'Z'
                }
                print(json.dumps(response))
                sys.stdout.flush()

            elif msg_type == 'command':
                # 處理命令
                cmd_type = message.get('commandType')
                payload = message.get('commandPayload', {})
                print(f"Received command: {cmd_type}, payload: {payload}", file=sys.stderr)

            elif msg_type == 'session_closed':
                # 清理資源
                print(f"Session {message['sessionId']} closed", file=sys.stderr)

            elif msg_type == 'session_unbound':
                # Session 解除綁定(Session 仍存在,但不再綁定到此擴展)
                print(f"Session {message['sessionId']} unbound", file=sys.stderr)

            elif msg_type == 'shutdown':
                # 準備關閉
                print("Shutdown requested", file=sys.stderr)
                sys.exit(0)

        except json.JSONDecodeError as e:
            print(f"JSON parse error: {e}", file=sys.stderr)
        except Exception as e:
            print(f"Error: {e}", file=sys.stderr)

if __name__ == '__main__':
    main()

🎯 實際範例:aspose-mcp-preview

aspose-mcp-preview 是一個完整的擴充功能實作範例,提供即時文檔預覽功能。透過 WebSocket 即時更新,讓 MCP 客戶端在編輯文檔時可同步在瀏覽器中查看 PNG、HTML 或 PDF 預覽。

功能展示

aspose-mcp-preview 操作展示

主要功能

快速安裝

extensions.json 中加入以下配置:

{
  "extensions": {
    "preview": {
      "command": {
        "type": "npx",
        "executable": "aspose-mcp-preview",
        "arguments": "--port 3000"
      },
      "inputFormats": ["png", "html", "pdf"],
      "supportedDocumentTypes": ["word", "excel", "powerpoint", "pdf"],
      "transportModes": ["stdin", "file", "mmap"],
      "preferredTransportMode": "stdin",
      "capabilities": {
        "supportsHeartbeat": true,
        "idleTimeoutMinutes": 0
      }
    }
  }
}

命令列選項

選項 預設值 說明
--port 3000 Web 伺服器連接埠
--host localhost Web 伺服器主機名稱
--no-open false 啟動時不自動開啟瀏覽器
--transport stdin 傳輸模式:stdin / file / mmap
📦 更多資訊

完整文檔、原始碼與更新請參考 GitHub - aspose-mcp-preview

📝 最佳實踐

握手協議

錯誤處理

效能優化

可靠性

🔗 相關資源