Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,25 @@ Parse the model from /info
```bash
bsc raw -a=true -i=192.168.128.101 -p=ABC01A000001 -m=GET -r="info" | jq '.data.result.model'
```

## MCP Agent

This package now includes a simple [Model Context Protocol](https://modelcontextprotocol.io) server. The agent exposes a few CLI commands as MCP tools so that other applications can trigger them.

Start the server:

```bash
npx bsc-agent
```

The agent currently provides tools to list players, fetch device info and reboot a player.

## Interactive Menu

Run an interactive menu for common tasks:

```bash
npx bsc-menu
```

The menu lets you add players, pick a configured player and then run commands such as checking device status or rebooting.
85 changes: 85 additions & 0 deletions interactive-cli.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
import os from 'os';
import inquirer from 'inquirer';
import handlers from './bin/handlerFunctions.js';

const CONFIG_FILE_PATH = path.join(os.homedir(), '.bsc', 'players.json');

function readPlayers() {
try {
return JSON.parse(fs.readFileSync(CONFIG_FILE_PATH, 'utf8'));
} catch {
return {};
}
}

async function mainMenu() {
const answer = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: 'Select an option',
choices: ['List players', 'Add player', 'Use player', 'Exit'],
},
]);
switch (answer.action) {
case 'List players':
await handlers.listPlayers();
return mainMenu();
case 'Add player':
await addPlayerFlow();
return mainMenu();
case 'Use player':
await selectPlayer();
return mainMenu();
default:
console.log('Goodbye!');
}
}

async function addPlayerFlow() {
const answers = await inquirer.prompt([
{ type: 'input', name: 'playerName', message: 'Player name:' },
{ type: 'input', name: 'ipAddress', message: 'IP address or hostname:' },
{ type: 'input', name: 'username', message: 'Username:', default: 'admin' },
{ type: 'password', name: 'password', message: 'Password:' },
{ type: 'input', name: 'storage', message: 'Storage location:', default: 'sd' },
]);
await handlers.addPlayer({ ...answers, verbose: false });
console.log(`Player ${answers.playerName} added.`);
}

async function selectPlayer() {
const players = readPlayers();
const names = Object.keys(players);
if (names.length === 0) {
console.log('No players configured. Add one first.');
return;
}
const { playerName } = await inquirer.prompt([
{ type: 'list', name: 'playerName', message: 'Choose a player', choices: names },
]);
await playerMenu(playerName);
}

async function playerMenu(playerName) {
const { action } = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: `Actions for ${playerName}`,
choices: ['Check device status', 'Reboot player', 'Back'],
},
]);
if (action === 'Check device status') {
await handlers.getDeviceInfo({ playerName, verbose: false, rawdata: false });
return playerMenu(playerName);
} else if (action === 'Reboot player') {
await handlers.reboot({ playerName, verbose: false, rawdata: false });
return playerMenu(playerName);
}
}

mainMenu();
53 changes: 53 additions & 0 deletions mcp-server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import handlers from "./bin/handlerFunctions.js";

const server = new McpServer({
name: "bsc-agent",
version: "1.0.0",
});

server.registerTool(
"listPlayers",
{
title: "List Players",
description: "List configured players",
inputSchema: {},
},
async () => {
await handlers.listPlayers();
return { content: [{ type: "text", text: "Listed players" }] };
}
);

server.registerTool(
"getDeviceInfo",
{
title: "Get Device Info",
description: "Get device info for a player",
inputSchema: { playerName: z.string() },
},
async ({ playerName }) => {
await handlers.getDeviceInfo({ playerName, verbose: false, rawdata: true });
return { content: [{ type: "text", text: "Fetched device info" }] };
}
);

server.registerTool(
"rebootPlayer",
{
title: "Reboot Player",
description: "Reboot a BrightSign player",
inputSchema: { playerName: z.string() },
},
async ({ playerName }) => {
await handlers.reboot({ playerName, verbose: false, rawdata: true });
return { content: [{ type: "text", text: "Player rebooted" }] };
}
);

const transport = new StdioServerTransport();
await server.connect(transport);

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"bin": {
"bsc": "./bin/index.js"
"bsc": "./bin/index.js",
"bsc-agent": "./mcp-server.mjs",
"bsc-menu": "./interactive-cli.mjs"
},
"author": "[email protected]",
"license": "ISC",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.13.0",
"digest-fetch": "^2.0.3",
"form-data": "^4.0.0",
"http-status": "^1.6.2",
"node-fetch": "^2.6.12",
"yargs": "^17.7.2"
"yargs": "^17.7.2",
"inquirer": "^9.2.16"
}
}