aodianyun-lcps-mcp
Version:
奥点云活动导播台工程MCP服务
1,046 lines (929 loc) • 44 kB
JavaScript
import {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js";
import { LCPSWebCall } from './help_func_LcpsWebCall.js';
// 解析命令行参数
function parseArgs() {
const args = process.argv.slice(2);
const config = {
accessId: null,
accessKey: null,
baseUrl: null,
domain: null
};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--access-id':
case '-i':
config.accessId = args[i + 1];
i++;
break;
case '--access-key':
case '-k':
config.accessKey = args[i + 1];
i++;
break;
case '--base-url':
case '-u':
config.baseUrl = args[i + 1];
i++;
break;
case '--domain':
case '-d':
config.domain = args[i + 1];
i++;
break;
case '--help':
case '-h':
console.log(`
奥点云活动导播台工程 MCP 服务
用法:
npx aodianyun-lcps-mcp [选项]
选项:
-i, --access-id <id> 奥点云访问ID
-k, --access-key <key> 奥点云访问密钥
-u, --base-url <url> API基础URL
-d, --domain <domain> 奥点云域名 (默认: jstest.aodianyun.cn)
-h, --help 显示帮助信息
环境变量:
AODIANYUN_ACCESS_ID 奥点云访问ID
AODIANYUN_ACCESS_KEY 奥点云访问密钥
AODIANYUN_BASE_URL API基础URL
AODIANYUN_DOMAIN 奥点云域名
示例:
npx aodianyun-lcps-mcp -i "481280170004" -k "66QxB9Y6b6020C1ouRWrLUSJZixwpYLW"
npx aodianyun-lcps-mcp --access-id "481280170004" --access-key "66QxB9Y6b6020C1ouRWrLUSJZixwpYLW" -d "prod.aodianyun.cn"
`);
process.exit(0);
break;
}
}
return config;
}
console.log("奥点云活动导播台工程 MCP 服务启动中...");
import fetch from "node-fetch";
import {z} from "zod";
import crypto from "crypto";
// 创建MCP服务器
const server = new McpServer({
name: "aodianyun-lcps-mcp",
version: "1.0.0"
});
// 获取命令行参数
const CLI_CONFIG = parseArgs();
// 获取域名配置
const DOMAIN = CLI_CONFIG.domain || process.env.AODIANYUN_DOMAIN || "jstest.aodianyun.cn";
// 鉴权配置
const AUTH_CONFIG = {
accessId: CLI_CONFIG.accessId || process.env.AODIANYUN_ACCESS_ID || "481280170004",
accessKey: CLI_CONFIG.accessKey || process.env.AODIANYUN_ACCESS_KEY || "66QxB9Y6b6020C1ouRWrLUSJZixwpYLW",
baseUrl: CLI_CONFIG.baseUrl || process.env.AODIANYUN_BASE_URL || `http://apaas-api.${DOMAIN}`
};
// 配置验证
function validateConfig() {
if (!AUTH_CONFIG.accessId || !AUTH_CONFIG.accessKey) {
console.error('❌ 缺少必要的配置参数');
console.error('请提供 accessId 和 accessKey 参数');
console.error('示例: npx aodianyun-lcps-mcp -i "481280170004" -k "66QxB9Y6b6020C1ouRWrLUSJZixwpYLW"');
return false;
}
console.log('✅ 配置验证通过了');
console.log(`🌐 域名: ${DOMAIN}`);
console.log(`📡 API地址: ${AUTH_CONFIG.baseUrl}`);
console.log(`🆔 访问ID: ${AUTH_CONFIG.accessId}`);
return true;
}
// 生成签名
function generateSignature(accessId, accessKey, signatureNonce, timestamp, body) {
const stringToSign = `${accessId}\n${accessKey}\n${signatureNonce}\n${timestamp}\n${body}`;
return crypto.createHash('md5').update(stringToSign).digest('hex');
}
// 生成请求头
function generateHeaders(body) {
const timestamp = Math.floor(Date.now() / 1000);
const signatureNonce = Math.random().toString(36).substring(2, 15);
const signature = generateSignature(
AUTH_CONFIG.accessId,
AUTH_CONFIG.accessKey,
signatureNonce,
timestamp,
body
);
return {
'Content-Type': 'application/json',
'X-AccessId': AUTH_CONFIG.accessId,
'X-TimeStamp': timestamp.toString(),
'X-SignatureNonce': signatureNonce,
'X-Signature': signature
};
}
// 通用API请求函数
async function makeApiRequest(endpoint, body) {
try {
const bodyString = JSON.stringify(body);
const headers = generateHeaders(bodyString);
const response = await fetch(`${AUTH_CONFIG.baseUrl}${endpoint}`, {
method: 'POST',
headers: headers,
body: bodyString
});
let str = JSON.stringify({
url: `${AUTH_CONFIG.baseUrl}${endpoint}`,
headers: headers,
body: bodyString,
response: response,
})
const data = await response.json();
return data;
} catch (error) {
console.error(`API请求出错 (${endpoint}):`, error);
throw error;
}
}
// 获取LCPS认证Token的辅助函数
async function getLcpsAuthToken(id, secretKey, pathname) {
try {
const authUrl = `https://lcpsconnect.${DOMAIN}/console/lcpsApp/getPwd`;
const authBody = {
id: id,
secretKey: secretKey,
pathname: pathname
};
const authResponse = await fetch(authUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(authBody)
});
const authResult = await authResponse.json();
if (authResult.Flag === 100) {
return {
success: true,
headers: {
'Content-Type': 'application/json',
'x-validate': authResult.data.validate,
'x-timestamp': authResult.data.timestamp.toString(),
'x-rand': authResult.data.rand.toString()
},
data: authResult.data
};
} else {
return {
success: false,
error: authResult.FlagString
};
}
} catch (error) {
return {
success: false,
error: error.message
};
}
}
// 发送websocket请求 并等待前端响应
async function sendWebSocket(lcpsAddr, responseText, cmd, param, actionName, timeout = 5) {
return new Promise((resolve, reject) => {
let wsAddr = lcpsAddr.replace("http://", "ws://")
wsAddr = wsAddr.replace("https://", "wss://")
let wsResponse = null;
let wsTimeout = null;
var lcpsCall = new LCPSWebCall(wsAddr, function () {
// WebSocket连接成功
responseText += `\n🔗 WebSocket连接成功\n`;
lcpsCall.Call(cmd, param, function (response) {
wsResponse = response;
const msg = response.msg || "未知错误";
const status = response.status || 0;
// 清除超时定时器
if (wsTimeout) {
clearTimeout(wsTimeout);
}
// 关闭连接并返回结果
lcpsCall.Close();
if(status !== 1){
responseText += `\n❌ ${actionName}接口调用失败: ${msg}\n`;
reject(responseText);
} else {
responseText += `\n📡 ${actionName}接口调用成功: ${msg}\n`;
resolve(responseText);
}
});
// 设置超时时间
wsTimeout = setTimeout(function () {
responseText += `\n⏰ ${actionName}调用超时(${timeout}秒)\n`;
lcpsCall.Close();
reject(responseText);
}, timeout * 1000);
});
// 设置连接超时
setTimeout(function () {
if (!wsResponse) {
responseText += `\nWebSocket连接超时\n`;
reject(responseText);
}
}, (timeout + 1) * 1000);
})
}
// 校验工程
async function authProject(projectId, responseText) {
return new Promise(async (resolve, reject) => {
responseText += `🔐 正在自动获取认证信息...\n`;
// 调用enterToolProject获取认证信息
const enterToolBody = {
projectId,
subType: "alcps"
};
try {
const enterToolResult = await makeApiRequest("/openMcp/activeLcps/enterToolProject", enterToolBody);
if (enterToolResult.flag === 100) {
responseText += `✅ 成功获取工程信息\n`;
// 从URL中解析认证信息
const urlData = enterToolResult.data.data;
const urlParams = new URLSearchParams(urlData.split('?')[1]);
const extractedId = urlParams.get('id');
const extractedSecretKey = urlParams.get('secretKey');
if (extractedId && extractedSecretKey) {
responseText += `🔐 解析到认证信息:\n`;
responseText += ` 🆔 ID: ${extractedId}\n`;
responseText += ` 🔑 SecretKey: ${extractedSecretKey}\n\n`;
resolve(responseText);
} else {
reject("无法从工程信息中解析认证参数");
}
} else {
reject(enterToolResult.flagString);
}
} catch (error) {
responseText += `❌ 自动获取认证信息时出错: ${error.message}\n\n`;
reject(responseText);
}
})
}
// 1. 创建活动导播台工程
server.tool(
"create_active_lcps_project",
{
title: z.string().describe("工程标题"),
inputNum: z.number().int().min(4).max(8).describe("信源数目(路数),可选值:4, 5, 6, 7, 8"),
resolution: z.enum(["720p", "1080p"]).describe("分辨率,可选值:720p, 1080p"),
useHours: z.number().int().min(1).default(1).describe("使用时长(小时),默认1小时"),
subType: z.string().default("alcps").describe("固定值alcps"),
remarks: z.string().optional().describe("备注信息(可选,默认自动生成resolution和inputNum的组合)")
},
async ({title, inputNum, resolution, useHours, subType, remarks}) => {
try {
// 自动生成remarks,如果用户没有提供的话
const autoRemarks = remarks || `${resolution}_${inputNum}路`;
const body = {
title,
inputNum,
resolution,
useHours,
subType,
remarks: autoRemarks
};
const result = await makeApiRequest("/openMcp/activeLcps/createProject", body);
let responseText = `🎬 创建活动导播台工程结果:\n\n`;
responseText += `📝 工程标题: ${title}\n`;
responseText += `📊 信源数目: ${inputNum}\n`;
responseText += `🖥️ 分辨率: ${resolution}\n`;
responseText += `⏰ 使用时长: ${useHours}小时\n`;
responseText += `📝 备注信息: ${autoRemarks}\n\n`;
if (result.flag === 100) {
responseText += `✅ 创建成功!\n`;
responseText += `🆔 工程ID: ${result.data.id}\n`;
responseText += `📋 状态: ${result.flagString}\n`;
} else {
responseText += `❌ 创建失败!\n`;
responseText += `⚠️ 错误信息: ${result.flagString}\n`;
}
return {
content: [{type: "text", text: responseText.trim()}]
};
} catch (error) {
return {
content: [{type: "text", text: `创建活动导播台工程出错: ${error.message}`}],
isError: true
};
}
}
);
// 2. 开启活动导播台工程
server.tool(
"open_active_lcps_project",
{
projectId: z.number().int().describe("工程ID"),
expiresTime: z.number().int().describe("到期时间(秒级时间戳)"),
subType: z.string().default("alcps").describe("固定值alcps")
},
async ({projectId, expiresTime, subType}) => {
try {
//调用enterToolProject获取中控页面链接
let responseText = `🚀 获取导播台中控页面:\n\n`;
const enterToolResult = await makeApiRequest("/openMcp/activeLcps/enterToolProject", {
projectId,
subType
});
if (enterToolResult.flag !== 100) {
responseText += `❌ 获取导播台中控页面失败:原因:${enterToolResult.flagString}\n`;
return {
content: [{type: "text", text: responseText.trim()}],
isError: true
};
}
responseText += `🚀 导播台中控页面:\n\n`;
// 生成消息通知的场景页地址
let pageUrl = enterToolResult.data.data;
let queryString = pageUrl.split("?")[1];
let urlObj = new URLSearchParams(queryString);
// 由于secretKey是导播台启动后才有的 因此需要删除掉 由导播台自动生成此参数
urlObj.delete("secretKey");
//重新拼接queryString
queryString = urlObj.toString();
let messagePage = "//cdn."+DOMAIN+ "/lcps-combine/apaas/index.html#/mcp?" + queryString
//准备参数
const body = {
projectId,
expiresTime,
subType,
messagePage
};
responseText += `🚀 开启活动导播台工程结果:\n\n`;
const result = await makeApiRequest("/openMcp/activeLcps/openProject", body);
responseText += `🆔 工程ID: ${projectId}\n`;
// 计算过期时间
const currentTime = new Date().getTime() + expiresTime * 1000;
responseText += `⏰ 到期时间: ${new Date(currentTime).toLocaleString()}\n\n`;
if (result.flag === 100) {
responseText += `✅ 开启成功!\n`;
responseText += `📋 状态: ${result.flagString}\n`;
if (result.data && result.data.lcpsAddr) {
responseText += `📡 LCPS地址: ${result.data.lcpsAddr}\n`;
responseText += `🔗 完整地址: ${result.data.lcpsAddr}\n`;
}
} else {
responseText += `❌ 开启失败!\n`;
responseText += `⚠️ 错误信息: ${result.flagString}\n`;
}
return {
content: [{type: "text", text: responseText.trim()}]
};
} catch (error) {
return {
content: [{type: "text", text: `开启活动导播台工程出错: ${error.message}`}],
isError: true
};
}
}
);
// 3. 关闭活动导播台工程
server.tool(
"close_active_lcps_project",
{
projectId: z.string().describe("工程ID"),
subType: z.string().default("alcps").describe("固定值alcps")
},
async ({projectId, subType}) => {
try {
const body = {
projectId,
subType
};
const result = await makeApiRequest("/openMcp/activeLcps/closeProject", body);
let responseText = `🛑 关闭活动导播台工程结果:\n\n`;
responseText += `🆔 工程ID: ${projectId}\n\n`;
if (result.flag === 100) {
responseText += `✅ 关闭成功!\n`;
responseText += `📋 状态: ${result.flagString}\n`;
} else {
responseText += `❌ 关闭失败!\n`;
responseText += `⚠️ 错误信息: ${result.flagString}\n`;
}
return {
content: [{type: "text", text: responseText.trim()}]
};
} catch (error) {
return {
content: [{type: "text", text: `关闭活动导播台工程出错: ${error.message}`}],
isError: true
};
}
}
);
// 4. 删除活动导播台工程
server.tool(
"delete_active_lcps_project",
{
projectId: z.string().describe("工程ID"),
subType: z.string().default("alcps").describe("固定值alcps")
},
async ({projectId, subType}) => {
try {
const body = {
projectId,
subType
};
const result = await makeApiRequest("/openMcp/activeLcps/deleteProject", body);
let responseText = `🗑️ 删除活动导播台工程结果:\n\n`;
responseText += `🆔 工程ID: ${projectId}\n\n`;
if (result.flag === 100) {
responseText += `✅ 删除成功!\n`;
responseText += `📋 状态: ${result.flagString}\n`;
} else {
responseText += `❌ 删除失败!\n`;
responseText += `⚠️ 错误信息: ${result.flagString}\n`;
}
return {
content: [{type: "text", text: responseText.trim()}]
};
} catch (error) {
return {
content: [{type: "text", text: `删除活动导播台工程出错: ${error.message}`}],
isError: true
};
}
}
);
// 5. 获取活动导播台工程列表
server.tool(
"get_active_lcps_project_list",
{
num: z.number().int().min(1).max(100).default(10).describe("每页数目,默认10,最大100"),
page: z.number().int().min(1).default(1).describe("页数,默认1"),
subType: z.string().default("alcps").describe("固定值alcps")
},
async ({num, page, subType}) => {
try {
const body = {
num,
page,
subType
};
const result = await makeApiRequest("/openMcp/activeLcps/getProjectList", body);
let responseText = `📋 获取活动导播台工程列表结果:\n\n`;
responseText += `📄 页码: ${page}\n`;
responseText += `📊 每页数量: ${num}\n\n`;
if (result.flag === 100) {
responseText += `✅ 获取成功!\n`;
responseText += `📋 状态: ${result.flagString}\n`;
responseText += `📈 总工程数: ${result.data.total}\n\n`;
if (result.data && Array.isArray(result.data)) {
responseText += `🎬 工程列表:\n`;
responseText += `─`.repeat(50) + `\n`;
result.data.forEach((project, index) => {
const statusText = project.status === 1 ? "🟢 开启" : "🔴 关闭";
const createTime = new Date(project.createtime * 1000).toLocaleString();
responseText += `${index + 1}. ${project.title}\n`;
responseText += ` 🆔 工程ID: ${project.id}\n`;
responseText += ` 📊 信源数目: ${project.inputNum}\n`;
responseText += ` 🖥️ 分辨率: ${project.resolution}\n`;
responseText += ` 📊 状态: ${statusText}\n`;
responseText += ` 📅 创建时间: ${createTime}\n`;
responseText += ` ─`.repeat(30) + `\n`;
});
} else {
responseText += `📝 暂无工程数据\n`;
}
} else {
responseText += `❌ 获取失败!\n`;
responseText += `⚠️ 错误信息: ${result.flagString}\n`;
}
return {
content: [{type: "text", text: responseText.trim()}]
};
} catch (error) {
return {
content: [{type: "text", text: `获取活动导播台工程列表出错: ${error.message}`}],
isError: true
};
}
}
);
// 6. 获取活动导播台中控页面
server.tool(
"get_active_lcps_control_page",
{
projectId: z.number().int().describe("工程ID"),
subType: z.string().default("alcps").describe("固定值alcps")
},
async ({projectId, subType}) => {
try {
const body = {
projectId,
subType
};
const result = await makeApiRequest("/openMcp/activeLcps/enterToolProject", body);
let responseText = `🎛️ 获取活动导播台中控页面结果:\n\n`;
responseText += `🆔 工程ID: ${projectId}\n\n`;
if (result.flag === 100) {
responseText += `✅ 获取成功!\n`;
responseText += `📋 状态: ${result.flagString}\n\n`;
responseText += `🔗 中控页面链接:\n`;
responseText += `${result.data.data}\n\n`;
responseText += `💡 提示: 复制上述链接到浏览器即可访问中控页面`;
// 提取认证信息
if (result.data && result.data.id && result.data.secretKey) {
responseText += `\n🔐 认证信息:\n`;
responseText += `🆔 ID: ${result.data.id}\n`;
responseText += `🔑 SecretKey: ${result.data.secretKey}\n`;
}
} else {
responseText += `❌ 获取失败!\n`;
responseText += `⚠️ 错误信息: ${result.flagString}\n`;
}
return {
content: [{type: "text", text: responseText.trim()}]
};
} catch (error) {
return {
content: [{type: "text", text: `获取活动导播台中控页面出错: ${error}`}],
isError: true
};
}
}
);
// 6.1. 获取LCPS认证Token
server.tool(
"get_lcps_auth_token",
{
id: z.string().describe("LCPS实例ID"),
secretKey: z.string().describe("LCPS密钥"),
pathname: z.string().default("/getToken").describe("认证路径,默认为/getToken"),
projectId: z.number().int().optional().describe("工程ID(用于enterToolProject调用)"),
subType: z.string().default("alcps").describe("固定值alcps")
},
async ({id, secretKey, pathname, projectId, subType}) => {
try {
let responseText = `🔐 获取LCPS认证Token结果:\n\n`;
responseText += `🆔 实例ID: ${id}\n`;
responseText += `🔑 密钥: ${secretKey}\n`;
responseText += `📂 路径: ${pathname}\n\n`;
// 1. 获取LCPS认证Token
const authUrl = `https://lcpsconnect.${DOMAIN}/console/lcpsApp/getPwd`;
const authBody = {
id: id,
secretKey: secretKey,
pathname: pathname
};
const authResponse = await fetch(authUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(authBody)
});
const authResult = await authResponse.json();
if (authResult.Flag === 100) {
responseText += `✅ LCPS认证成功!\n`;
responseText += `📋 状态: ${authResult.FlagString}\n\n`;
responseText += `🔐 认证信息:\n`;
responseText += ` Validate: ${authResult.data.validate}\n`;
responseText += ` Timestamp: ${authResult.data.timestamp}\n`;
responseText += ` Rand: ${authResult.data.rand}\n\n`;
} else {
responseText += `❌ LCPS认证失败!\n`;
responseText += `⚠️ 错误信息: ${authResult.FlagString}\n\n`;
}
// 2. 如果提供了projectId,调用enterToolProject
if (projectId) {
responseText += `🎛️ 调用enterToolProject...\n`;
const enterToolBody = {
projectId,
subType
};
const enterToolResult = await makeApiRequest("/openMcp/activeLcps/enterToolProject", enterToolBody);
if (enterToolResult.flag === 100) {
responseText += `✅ enterToolProject调用成功!\n`;
responseText += `📋 状态: ${enterToolResult.flagString}\n\n`;
responseText += `🔗 中控页面链接:\n`;
responseText += `${enterToolResult.data.data}\n\n`;
// 从data.data字符串中解析id和secretKey
try {
const urlData = enterToolResult.data.data;
const urlParams = new URLSearchParams(urlData.split('?')[1]);
const extractedId = urlParams.get('id');
const extractedSecretKey = urlParams.get('secretKey');
const subToken = urlParams.get('subToken');
const userCode = urlParams.get('userCode');
if (extractedId && extractedSecretKey) {
responseText += `🔐 解析到的认证信息:\n`;
responseText += ` 🆔 ID: ${extractedId}\n`;
responseText += ` 🔑 SecretKey: ${extractedSecretKey}\n`;
responseText += ` 🎫 SubToken: ${subToken}\n`;
responseText += ` 👤 UserCode: ${userCode}\n\n`;
// 更新id和secretKey为解析出的值
id = extractedId;
secretKey = extractedSecretKey;
responseText += `🔄 已更新认证参数,将使用解析出的ID和SecretKey重新获取认证Token\n\n`;
// 使用解析出的认证信息重新获取LCPS认证Token
const updatedAuthBody = {
id: extractedId,
secretKey: extractedSecretKey,
pathname: pathname
};
const updatedAuthResponse = await fetch(authUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedAuthBody)
});
const updatedAuthResult = await updatedAuthResponse.json();
if (updatedAuthResult.Flag === 100) {
responseText += `✅ 使用解析的认证信息重新获取Token成功!\n`;
responseText += `🔐 更新后的认证信息:\n`;
responseText += ` Validate: ${updatedAuthResult.data.validate}\n`;
responseText += ` Timestamp: ${updatedAuthResult.data.timestamp}\n`;
responseText += ` Rand: ${updatedAuthResult.data.rand}\n\n`;
} else {
responseText += `❌ 使用解析的认证信息重新获取Token失败: ${updatedAuthResult.FlagString}\n\n`;
}
} else {
responseText += `⚠️ 无法从URL中解析出ID和SecretKey\n\n`;
}
} catch (error) {
responseText += `❌ 解析URL参数时出错: ${error.message}\n\n`;
}
} else {
responseText += `❌ enterToolProject调用失败!\n`;
responseText += `⚠️ 错误信息: ${enterToolResult.flagString}\n\n`;
}
}
responseText += `💡 提示: 使用认证信息作为请求头 x-validate, x-timestamp, x-rand`;
return {
content: [{type: "text", text: responseText.trim()}]
};
} catch (error) {
return {
content: [{type: "text", text: `获取LCPS认证Token出错: ${error.message}`}],
isError: true
};
}
}
);
// 7. 设置信道参数
server.tool(
"set_channel",
{
lcpsAddr: z.string().describe("LCPS地址(从open_active_lcps_project返回的lcpsAddr,支持带http协议)"),
channel: z.number().int().min(1).max(16).describe("信道号(1-16,对应API的0-15)"),
keepRatio: z.boolean().optional().describe("是否保持视频比例"),
playURL: z.array(z.string()).optional().describe("拉流地址数组"),
inputType: z.string().default("拉流").describe("输入类型,默认为拉流"),
allowPublishForce: z.boolean().default(false).describe("是否允许强制发布,默认false"),
firstBufferTime: z.number().int().default(0).describe("首次缓冲时间(秒),默认0"),
maxBufferTime: z.number().int().default(1).describe("最大缓冲时间(秒),默认1"),
smoothMode: z.boolean().default(false).describe("平滑模式,默认false"),
enableLowLatency: z.boolean().default(false).describe("启用低延迟,默认false"),
id: z.string().optional().describe("LCPS实例ID(从get_active_lcps_control_page获取)"),
secretKey: z.string().optional().describe("LCPS密钥(从get_active_lcps_control_page获取)"),
projectId: z.number().int().optional().describe("工程ID(用于自动获取认证信息)")
},
async ({lcpsAddr, channel, keepRatio, playURL, inputType, allowPublishForce, firstBufferTime, maxBufferTime, smoothMode, enableLowLatency, id, secretKey, projectId}) => {
let responseText = `🎛️ 批量设置信道参数结果:\n\n`;
try {
// 将用户输入的1-16转换为API需要的0-15
responseText += `📡 LCPS地址: ${lcpsAddr}\n`;
responseText += `📺 用户信道号: ${channel} \n\n`;
const results = [];
let authHeaders = { 'Content-Type': 'application/json' };
// 自动获取认证信息
let finalId = id;
let finalSecretKey = secretKey;
// 如果提供了projectId,自动调用get_lcps_auth_token获取认证信息
if (projectId && (!id || !secretKey)) {
responseText = await authProject(projectId, responseText);
}
// 如果有了认证信息,显示状态
if (finalId && finalSecretKey) {
responseText += `🔐 认证信息已准备,将在各API调用时获取对应Token\n\n`;
} else {
responseText += `⚠️ 未提供认证信息,将使用无认证模式\n\n`;
}
//发送ws消息
responseText = await sendWebSocket(lcpsAddr, responseText, "setChannel", {
channel: channel,
keepRatio: keepRatio || false,
playURL: playURL || [],
inputType,
allowPublishForce,
firstBufferTime,
maxBufferTime,
smoothMode,
enableLowLatency
}, "设置信道", 10)
return {
content: [{type: "text", text: responseText.trim()}]
};
} catch (error) {
responseText += `\n❌ 设置信道参数出错: ${error}\n`;
return {
content: [{type: "text", text: responseText.trim()}],
isError: true
};
}
}
);
// 8. 设置导播台输出地址
server.tool(
"set_output_address",
{
lcpsAddr: z.string().describe("LCPS地址(从open_active_lcps_project返回的lcpsAddr,支持带http协议)"),
publishURL: z.array(z.string()).describe("推流地址数组"),
formatIndex: z.number().int().default(0).describe("格式索引,默认0"),
notRestart: z.boolean().default(false).describe("是否不重启,默认false"),
enable: z.boolean().default(true).describe("是否启用,默认true"),
playURL: z.string().default("").describe("播放地址,默认为空"),
videoSize: z.string().default("1920x1080").describe("视频尺寸,默认1920x1080"),
videoBitrate: z.string().default("3000k").describe("视频码率,默认3000k"),
videoCBR: z.boolean().default(false).describe("视频CBR模式,默认false"),
qmin: z.number().int().default(2).describe("最小量化参数,默认2"),
qmax: z.number().int().default(1024).describe("最大量化参数,默认1024"),
gop: z.number().int().default(3).describe("GOP大小,默认3"),
audioBitrate: z.string().default("128k").describe("音频码率,默认128k"),
audioOnly: z.boolean().default(false).describe("仅音频模式,默认false"),
enableDelayPublish: z.boolean().default(false).describe("启用延迟发布,默认false"),
delayPublishTime: z.number().int().default(120).describe("延迟发布时间(秒),默认120"),
delayPublishURL: z.array(z.string()).default([]).describe("延迟发布地址数组,默认为空"),
publishToLPS: z.boolean().default(false).describe("发布到LPS,默认false"),
videoRecord: z.boolean().default(false).describe("视频录制,默认false"),
lossless: z.boolean().default(false).describe("无损模式,默认false"),
hdr: z.boolean().default(false).describe("HDR模式,默认false"),
pixfmt: z.string().default("yuv420").describe("像素格式,默认yuv420"),
hdrtype: z.string().default("HLG").describe("HDR类型,默认HLG"),
id: z.string().optional().describe("LCPS实例ID(从get_active_lcps_control_page获取)"),
secretKey: z.string().optional().describe("LCPS密钥(从get_active_lcps_control_page获取)"),
projectId: z.number().int().optional().describe("工程ID(用于自动获取认证信息)")
},
async ({lcpsAddr, publishURL, formatIndex, notRestart, enable, playURL, videoSize, videoBitrate, videoCBR, qmin, qmax, gop, audioBitrate, audioOnly, enableDelayPublish, delayPublishTime, delayPublishURL, publishToLPS, videoRecord, lossless, hdr, pixfmt, hdrtype, id, secretKey, projectId}) => {
let responseText = `📡 设置输出地址结果:\n\n`;
try {
responseText += `📡 LCPS地址: ${lcpsAddr}\n`;
responseText += `🔗 推流地址: ${publishURL.join(', ')}\n`;
responseText += `🎬 视频尺寸: ${videoSize}\n`;
responseText += `📊 视频码率: ${videoBitrate}\n`;
responseText += `🎵 音频码率: ${audioBitrate}\n\n`;
// 自动获取认证信息
let finalId = id;
let finalSecretKey = secretKey;
// 如果提供了projectId,自动调用get_lcps_auth_token获取认证信息
if (projectId && (!id || !secretKey)) {
responseText = await authProject(projectId, responseText);
}
// 如果有了认证信息,显示状态
if (finalId && finalSecretKey) {
responseText += `🔐 认证信息已准备,将在API调用时获取对应Token\n\n`;
} else {
responseText += `⚠️ 未提供认证信息,将使用无认证模式\n\n`;
}
//发送ws消息
responseText = await sendWebSocket(lcpsAddr, responseText, "changeOutput", {
formatIndex,
notRestart,
enable,
publishURL,
playURL,
videoSize,
videoBitrate,
videoCBR,
qmin,
qmax,
gop,
audioBitrate,
audioOnly,
enableDelayPublish,
delayPublishTime,
delayPublishURL,
publishToLPS,
videoRecord,
lossless,
hdr,
pixfmt,
hdrtype
}, "设置导播台输出地址", 5);
return {
content: [{type: "text", text: responseText.trim()}]
};
} catch (error) {
responseText += `\n❌ 设置输出地址出错: ${error}\n`;
return {
content: [{type: "text", text: responseText.trim()}],
isError: true
};
}
}
);
// 9. 信道切换
server.tool(
"switch_channel",
{
lcpsAddr: z.string().describe("LCPS地址(从open_active_lcps_project返回的lcpsAddr,支持带http协议)"),
channel: z.number().int().describe("信道号(1-16,对应API的0-15"),
id: z.string().optional().describe("LCPS实例ID(从get_active_lcps_control_page获取)"),
secretKey: z.string().optional().describe("LCPS密钥(从get_active_lcps_control_page获取)"),
projectId: z.number().int().optional().describe("工程ID(用于自动获取认证信息)")
},
async ({lcpsAddr, channel, id, secretKey, projectId}) => {
let responseText = `📡 信道切换结果:\n\n`;
try {
responseText += `📡 LCPS地址: ${lcpsAddr}\n`;
responseText += `🔗 切到新信道: ${channel}\n`;
// 自动获取认证信息
let finalId = id;
let finalSecretKey = secretKey;
// 如果提供了projectId,自动调用get_lcps_auth_token获取认证信息
if (projectId && (!id || !secretKey)) {
responseText = await authProject(projectId, responseText);
}
// 如果有了认证信息,显示状态
if (finalId && finalSecretKey) {
responseText += `🔐 认证信息已准备,将在各API调用时获取对应Token\n\n`;
} else {
responseText += `⚠️ 未提供认证信息,将使用无认证模式\n\n`;
}
//发送ws消息
responseText = await sendWebSocket(lcpsAddr, responseText, "switchChannel", {
channel: channel
}, "信道切换", 5)
return {
content: [{type: "text", text: responseText.trim()}]
};
} catch (error) {
responseText += `\n❌ 信道切换出错: ${error}\n`;
return {
content: [{type: "text", text: responseText.trim()}],
isError: true
};
}
}
);
// 10.设置垫片
server.tool(
"set_gasket_config",
{
lcpsAddr: z.string().describe("LCPS地址(从open_active_lcps_project返回的lcpsAddr,支持带http协议)"),
channel: z.number().int().describe("信道号(1-16,对应API的0-15"),
id: z.string().optional().describe("LCPS实例ID(从get_active_lcps_control_page获取)"),
secretKey: z.string().optional().describe("LCPS密钥(从get_active_lcps_control_page获取)"),
projectId: z.number().int().optional().describe("工程ID(用于自动获取认证信息)"),
url: z.string().describe("垫片地址"),
title: z.string().optional().describe("垫片标题"),
thumbnail: z.string().optional().describe("垫片封面")
},
async ({lcpsAddr, channel, id, secretKey, projectId, url, title, thumbnail}) => {
let responseText = `📡 设置垫片结果:\n\n`;
try {
responseText += `📡 LCPS地址: ${lcpsAddr}\n`;
responseText += `🔗 垫片地址: ${url}\n`;
if(title == undefined || title == "") {
title = "垫片";
}
responseText += `🔗 垫片名称: ${title}\n`;
if(thumbnail == undefined || thumbnail == "") {
thumbnail = "";
}
responseText += `🔗 垫片封面图: ${thumbnail}\n`;
// 自动获取认证信息
let finalId = id;
let finalSecretKey = secretKey;
// 如果提供了projectId,自动调用get_lcps_auth_token获取认证信息
if (projectId && (!id || !secretKey)) {
responseText = await authProject(projectId, responseText);
}
// 如果有了认证信息,显示状态
if (finalId && finalSecretKey) {
responseText += `🔐 认证信息已准备,将在各API调用时获取对应Token\n\n`;
} else {
responseText += `⚠️ 未提供认证信息,将使用无认证模式\n\n`;
}
//发送ws消息
responseText = await sendWebSocket(lcpsAddr, responseText, "setGasketConfig", {
channel,
url,
title,
thumbnail
}, "设置垫片", 5)
return {
content: [{type: "text", text: responseText.trim()}]
};
} catch (error) {
responseText += `\n❌ 信道切换出错: ${error}\n`;
return {
content: [{type: "text", text: responseText.trim()}],
isError: true
};
}
}
);
// 启动服务器
async function main() {
try {
console.log("启动活动导播台MCP服务器...");
// 验证配置
if (!validateConfig()) {
process.exit(1);
}
const transport = new StdioServerTransport();
await server.connect(transport);
console.log("活动导播台MCP服务器已经开始等待连接");
} catch (error) {
console.error("启动服务器时出错:", error);
process.exit(1);
}
}
main();