了解如何開發擴展程序訂閱 Session 變更
擴充功能系統讓外部程序訂閱 Aspose MCP Server 的 Session 變更事件。當 Session 中的文件被修改時,系統會自動將文件快照發送給已綁定的擴充功能程序,開發者可據此實現各種自訂應用。
伺服器啟動 → 載入擴展配置 → MCP 就緒
↓
背景初始化(異步)
↓
啟動所有擴展程序 → 握手交換元資料
↓
MCP Client → extension(bind) → ExtensionSessionBridge ←─┘
↓
Session 變更 → DocumentConversionService → 快照
↓
Extension Process (Node.js/Python/etc.)
↓
接收快照 → 處理 → 回應 ACK
Initializing 狀態Idle 狀態,可透過綁定接收 Session 快照,處理後回應 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"} │
│─────────────────────────────────────────────>│
│ │
│ 握手完成,開始正常運作 │
擴充功能系統支援三種傳輸模式,用於將快照資料發送給擴充功能程序:
擴展程序透過標準輸入輸出與 MCP Server 通訊。消息格式為 JSON,每行一條消息。
擴展啟動時必須先完成握手協議(initialize → initialize_response → initialized),才會收到以下消息。
MCP 客戶端可透過 extension 工具管理擴展和綁定。
{
"operation": "list"
}
{
"operation": "bind",
"sessionId": "abc123",
"extensionId": "pdf-viewer",
"format": "pdf"
}
{
"operation": "unbind",
"sessionId": "abc123",
"extensionId": "pdf-viewer"
}
{
"operation": "status",
"extensionId": "pdf-viewer"
}
{
"operation": "set_format",
"sessionId": "abc123",
"extensionId": "pdf-viewer",
"format": "html"
}
{
"operation": "bindings",
"sessionId": "abc123"
}
{
"operation": "command",
"sessionId": "abc123",
"extensionId": "pdf-viewer",
"command": "goto",
"data": { "page": 5 }
}
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...');
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 是一個完整的擴充功能實作範例,提供即時文檔預覽功能。透過 WebSocket 即時更新,讓 MCP 客戶端在編輯文檔時可同步在瀏覽器中查看 PNG、HTML 或 PDF 預覽。
在 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
initialize 後盡快回應 initialize_responsename 和 version 欄位為必填,建議版本號使用 semver 格式initialized 之前不要處理其他消息extensions_example.json
- 完整配置範例