# Directory Structure
```
├── .gitignore
├── dist
│ ├── index.js
│ ├── run-tests.js
│ ├── test-agent-proxy.js
│ ├── test-cursor.js
│ ├── test-direct.js
│ ├── test-jsonrpc.js
│ ├── test-mcp-client.js
│ └── test.js
├── package-lock.json
├── package.json
├── readme.md
├── src
│ ├── index.ts
│ ├── run-tests.ts
│ ├── test-agent-proxy.ts
│ ├── test-cursor.ts
│ ├── test-direct.ts
│ ├── test-jsonrpc.ts
│ ├── test-mcp-client.ts
│ └── test.ts
├── test-client.js
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
node_modules
.pnp
.pnp.js
# testing
coverage
secrets.txt
# next.js
.next/
out/
next-env.d.ts
# production
build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
# turbo
.turbo
# ide
.idea/
.vscode/
.zed
# contentlayer
.contentlayer/
```
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
```markdown
# Brightsy MCP Server
This is a Model Context Protocol (MCP) server that connects to an Brightsy AI agent.
## Installation
```bash
npm install
```
## Usage
To start the server:
```bash
npm start -- --agent-id=<your-agent-id> --api-key=<your-api-key>
```
Or with positional arguments:
```bash
npm start -- <your-agent-id> <your-api-key> [tool-name] [message]
```
You can also provide an initial message to be sent to the agent:
```bash
npm start -- --agent-id=<your-agent-id> --api-key=<your-api-key> --message="Hello, agent!"
```
### Customizing the Tool Name
By default, the MCP server registers a tool named "brightsy". You can customize this name using the `--tool-name` parameter:
```bash
npm start -- --agent-id=<your-agent-id> --api-key=<your-api-key> --tool-name=<custom-tool-name>
```
You can also set the tool name as the third positional argument:
```bash
npm start -- <your-agent-id> <your-api-key> <custom-tool-name>
```
Or using the `BRIGHTSY_TOOL_NAME` environment variable:
```bash
export BRIGHTSY_TOOL_NAME=custom-tool-name
npm start -- --agent-id=<your-agent-id> --api-key=<your-api-key>
```
### Environment Variables
The following environment variables can be used to configure the server:
- `BRIGHTSY_AGENT_ID`: The agent ID to use (alternative to command line argument)
- `BRIGHTSY_API_KEY`: The API key to use (alternative to command line argument)
- `BRIGHTSY_TOOL_NAME`: The tool name to register (default: "brightsy")
## Testing the agent_proxy Tool
The agent_proxy tool allows you to proxy requests to an Brightsy AI agent. To test this tool, you can use the provided test scripts.
### Prerequisites
Before running the tests, set the following environment variables:
```bash
export AGENT_ID=your-agent-id
export API_KEY=your-api-key
# Optional: customize the tool name for testing
export TOOL_NAME=custom-tool-name
```
Alternatively, you can pass these values as command-line arguments:
```bash
# Using named arguments
npm run test:cli -- --agent-id=your-agent-id --api-key=your-api-key --tool-name=custom-tool-name
# Using positional arguments
npm run test:cli -- your-agent-id your-api-key custom-tool-name
```
### Running the Tests
To run all tests:
```bash
npm test
```
To run specific tests:
```bash
# Test using the command line interface
npm run test:cli
# Test using the direct MCP protocol
npm run test:direct
```
### Test Scripts
1. **Command Line Test** (`test-agent-proxy.ts`): Tests the agent_proxy tool by running the MCP server with a test message.
2. **Direct MCP Protocol Test** (`test-direct.ts`): Tests the agent_proxy tool by sending a properly formatted MCP request directly to the server.
## How the Tool Works
The MCP server registers a tool (named "brightsy" by default) that forwards requests to an OpenAI-compatible AI agent and returns the response. It takes a `messages` parameter, which is an array of message objects with `role` and `content` properties.
Example usage in an MCP client:
```javascript
// Using the default tool name
const response = await client.callTool("brightsy", {
messages: [
{
role: "user",
content: "Hello, can you help me with a simple task?"
}
]
});
// Or using a custom tool name if configured
const response = await client.callTool("custom-tool-name", {
messages: [
{
role: "user",
content: "Hello, can you help me with a simple task?"
}
]
});
```
The response will contain the agent's reply in the `content` field.
```
--------------------------------------------------------------------------------
/src/test-mcp-client.ts:
--------------------------------------------------------------------------------
```typescript
// This file is intentionally left empty to avoid build errors.
// We're using test-cursor.ts instead.
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "es2020",
"module": "nodenext",
"moduleResolution": "nodenext",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "brightsy-mcp",
"version": "1.0.2",
"description": "MCP server that connects to an OpenAI-compatible AI agent",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"start": "npm run build && node dist/index.js",
"test": "npm run build && node dist/run-tests.js",
"test:direct": "npm run build && node dist/test-direct.js",
"test:cli": "npm run build && node dist/test-agent-proxy.js",
"test:cursor": "npm run build && node dist/test-cursor.js",
"test:jsonrpc": "npm run build && node dist/test-jsonrpc.js"
},
"bin": {
"brightsy-mcp": "dist/index.js"
},
"files": [
"dist"
],
"dependencies": {
"@modelcontextprotocol/sdk": "^1.6.1",
"zod": "^3.21.4"
},
"devDependencies": {
"@types/node": "^18.15.11",
"typescript": "^5.0.4"
}
}
```
--------------------------------------------------------------------------------
/src/run-tests.ts:
--------------------------------------------------------------------------------
```typescript
/**
* This script builds and runs all the test scripts.
*/
import { execSync } from "child_process";
import { existsSync } from "fs";
import { join } from "path";
// Configuration from environment variables
const AGENT_ID = process.env.AGENT_ID || process.env.BRIGHTSY_AGENT_ID || '';
const API_KEY = process.env.API_KEY || process.env.BRIGHTSY_API_KEY || '';
const TOOL_NAME = process.env.TOOL_NAME || process.env.BRIGHTSY_TOOL_NAME || "brightsy";
// Validate required environment variables
if (!AGENT_ID || !API_KEY) {
console.error('Error: Required environment variables not set');
console.error('Please set the following environment variables:');
console.error(' AGENT_ID or BRIGHTSY_AGENT_ID: The agent ID to use for testing');
console.error(' API_KEY or BRIGHTSY_API_KEY: The API key to use for testing');
console.error(' TOOL_NAME or BRIGHTSY_TOOL_NAME: (optional) The tool name to use (default: brightsy)');
process.exit(1);
}
// Ensure we're in the right directory
const packageJsonPath = join(process.cwd(), "package.json");
if (!existsSync(packageJsonPath)) {
console.error("Error: package.json not found. Make sure you're running this from the brightsy-mcp directory.");
process.exit(1);
}
// Build the project
console.log("Building the project...");
try {
execSync("npm run build", { stdio: "inherit" });
console.log("Build successful!");
} catch (error) {
console.error("Build failed:", error);
process.exit(1);
}
// Run the tests with environment variables
const env = {
...process.env,
AGENT_ID,
API_KEY,
TOOL_NAME
};
console.log("\n=== Running MCP stdio server tests ===");
try {
execSync("node dist/test.js", {
stdio: "inherit",
timeout: 60000, // 60 second timeout
env
});
} catch (error) {
console.error("MCP stdio server tests failed:", error);
}
console.log("\n=== Running direct MCP protocol test ===");
try {
execSync("node dist/test-direct.js", {
stdio: "inherit",
timeout: 60000, // 60 second timeout
env
});
} catch (error) {
console.error("Direct MCP protocol test failed:", error);
}
console.log("\nAll tests completed.");
```
--------------------------------------------------------------------------------
/src/test-jsonrpc.ts:
--------------------------------------------------------------------------------
```typescript
/**
* This script tests the agent_proxy tool by sending a JSON-RPC request to the MCP server.
*/
import { spawn } from "child_process";
// Configuration
const agentId = process.env.AGENT_ID || "your-agent-id";
const apiKey = process.env.API_KEY || "your-api-key";
console.log(`Starting JSON-RPC test with agent ID: ${agentId}`);
// Spawn the MCP server process
const serverProcess = spawn("node", [
"dist/index.js",
"--agent-id", agentId,
"--api-key", apiKey
], {
cwd: process.cwd(),
stdio: ["pipe", "pipe", "inherit"] // We'll write to stdin and read from stdout
});
// Create a JSON-RPC request to call the agent-proxy tool
const jsonRpcRequest = {
jsonrpc: "2.0",
id: "test-1",
method: "tools/call",
params: {
name: "agent-proxy",
arguments: {
messages: [
{
role: "user",
content: "I'm testing the agent_proxy tool in the brightsy-mcp project. Can you confirm that you're receiving this message through the agent_proxy tool and respond with a simple greeting?"
}
]
}
}
};
// Wait for the server to start up
setTimeout(() => {
console.log("Sending JSON-RPC request to MCP server...");
// Send the request to the server
serverProcess.stdin.write(JSON.stringify(jsonRpcRequest) + "\n");
}, 2000);
// Process the response
let responseData = "";
serverProcess.stdout.on("data", (data) => {
responseData += data.toString();
try {
// Try to parse the response as JSON
const response = JSON.parse(responseData);
console.log("Received JSON-RPC response:");
console.log(JSON.stringify(response, null, 2));
// Clean up and exit
serverProcess.kill();
process.exit(0);
} catch (error) {
// If we can't parse it yet, wait for more data
}
});
// Handle process events
serverProcess.on("error", (error) => {
console.error("Failed to start server process:", error);
process.exit(1);
});
serverProcess.on("exit", (code, signal) => {
if (code !== null && code !== 0) {
console.error(`Server process exited with code ${code}`);
process.exit(code);
}
});
// Handle termination signals
process.on("SIGINT", () => {
console.log("Received SIGINT, terminating server...");
serverProcess.kill("SIGINT");
process.exit(0);
});
// Set a timeout to prevent hanging
setTimeout(() => {
console.error("Test timed out after 30 seconds");
serverProcess.kill();
process.exit(1);
}, 30000);
```
--------------------------------------------------------------------------------
/src/test-cursor.ts:
--------------------------------------------------------------------------------
```typescript
/**
* This script tests the agent_proxy tool with Cursor.
* It sets up a simple HTTP server that can receive requests from Cursor's MCP client.
*/
import { createServer } from 'http';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Configuration
const agentId = process.env.AGENT_ID || "your-agent-id";
const apiKey = process.env.API_KEY || "your-api-key";
const toolName = process.env.TOOL_NAME || "agent-proxy";
const PORT = 3333;
console.log(`Starting test with agent ID: ${agentId}`);
console.log(`Using tool name: ${toolName}`);
// Create server instance
const server = new McpServer({
name: "cursor-test-mcp",
version: "1.0.0",
});
// Register the agent proxy tool
server.tool(
toolName,
"Proxy requests to an OpenAI-compatible AI agent",
{
messages: z.array(
z.object({
role: z.string().describe("The role of the message sender"),
content: z.union([z.string(), z.array(z.any())]).describe("The content of the message")
})
).describe("The messages to send to the agent")
},
async ({ messages }) => {
console.log("Agent proxy tool called with messages:", JSON.stringify(messages, null, 2));
// For testing purposes, just echo back the message
return {
content: [
{
type: "text",
text: `Received message: "${messages[0]?.content || 'No message'}"`,
},
],
};
},
);
// Start the server
async function main() {
try {
const transport = new StdioServerTransport();
// Connect to the transport
console.log(`Connecting to transport...`);
await server.connect(transport);
console.log(`Test MCP Server running on stdio`);
console.log(`Registered tool name: ${toolName}`);
console.log(`Ready to receive requests`);
// Keep the process running
process.stdin.resume();
// Handle termination signals
process.on('SIGINT', () => {
console.log('Received SIGINT signal, shutting down...');
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('Received SIGTERM signal, shutting down...');
process.exit(0);
});
} catch (error) {
console.error("Error starting server:", error);
process.exit(1);
}
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/src/test-agent-proxy.ts:
--------------------------------------------------------------------------------
```typescript
import { spawn } from "child_process";
// Configuration
const agentId = process.env.AGENT_ID || "your-agent-id";
const apiKey = process.env.API_KEY || "your-api-key";
const testMessage = "Hello, can you help me with a simple task?";
const toolName = process.env.TOOL_NAME || "brightsy";
console.log(`Starting test with agent ID: ${agentId}`);
console.log(`Using tool name: ${toolName}`);
console.log(`Test message: "${testMessage}"`);
// Check if command line arguments were provided
const args = process.argv.slice(2);
let cmdAgentId = agentId;
let cmdApiKey = apiKey;
let cmdToolName = toolName;
// Parse command line arguments
for (let i = 0; i < args.length; i++) {
const arg = args[i];
// Handle arguments with equals sign (--key=value)
if (arg.startsWith('--') && arg.includes('=')) {
const [key, value] = arg.substring(2).split('=', 2);
if (key === 'agent-id' || key === 'agent_id') {
cmdAgentId = value;
} else if (key === 'api-key' || key === 'api_key') {
cmdApiKey = value;
} else if (key === 'tool-name' || key === 'tool_name') {
cmdToolName = value;
}
}
// Handle arguments with space (--key value)
else if (arg.startsWith('--')) {
const key = arg.substring(2);
const nextArg = i + 1 < args.length ? args[i + 1] : undefined;
if (nextArg && !nextArg.startsWith('--')) {
if (key === 'agent-id' || key === 'agent_id') {
cmdAgentId = nextArg;
} else if (key === 'api-key' || key === 'api_key') {
cmdApiKey = nextArg;
} else if (key === 'tool-name' || key === 'tool_name') {
cmdToolName = nextArg;
}
i++; // Skip the next argument as we've used it as a value
}
}
// Handle positional arguments
else if (i === 0) {
cmdAgentId = arg;
} else if (i === 1) {
cmdApiKey = arg;
} else if (i === 2) {
cmdToolName = arg;
}
}
if (cmdAgentId !== agentId || cmdApiKey !== apiKey || cmdToolName !== toolName) {
console.log(`Using command line arguments: agent_id=${cmdAgentId}, tool_name=${cmdToolName}`);
}
// Spawn the MCP server process with the test message
const serverProcess = spawn("node", [
"dist/index.js",
cmdAgentId,
cmdApiKey,
cmdToolName,
testMessage
], {
cwd: process.cwd(),
stdio: "inherit" // Inherit stdio to see output directly
});
// Handle process events
serverProcess.on("error", (error) => {
console.error("Failed to start server process:", error);
process.exit(1);
});
serverProcess.on("exit", (code, signal) => {
console.log(`Server process exited with code ${code} and signal ${signal}`);
process.exit(code || 0);
});
// Handle termination signals
process.on("SIGINT", () => {
console.log("Received SIGINT, terminating server...");
serverProcess.kill("SIGINT");
});
process.on("SIGTERM", () => {
console.log("Received SIGTERM, terminating server...");
serverProcess.kill("SIGTERM");
});
```
--------------------------------------------------------------------------------
/test-client.js:
--------------------------------------------------------------------------------
```javascript
import { Client as McpClient } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { spawn } from "child_process";
import * as path from "path";
async function runClient() {
console.log("Starting MCP client test with main server...");
// Create a transport connected to stdio
const transport = new StdioClientTransport({
command: "/opt/homebrew/bin/node",
args: [
path.resolve("dist/index.js"),
"--agent-id", "e6b93840-e117-4390-a9e6-0d78f5c5bbcf",
"--api-key", "d5243ff6-caa5-4456-808e-3a0d60a84e5e",
"--tool-name", "brightsy"
],
env: {
BRIGHTSY_TEST_MODE: "true",
BRIGHTSY_MAINTAIN_HISTORY: "true"
}
});
// Create MCP client
const client = new McpClient({
name: "test-client",
version: "1.0.0"
});
try {
// Connect to the server
console.log("Connecting to server...");
await client.connect(transport);
console.log("Connected to server");
// Test messages to send with conversation IDs
const testConversations = [
{ id: "test-convo-1", messages: ["test:echo Hello in conversation 1", "test:echo Second message in conversation 1"] },
{ id: "test-convo-2", messages: ["test:echo Hello in conversation 2"] },
{ id: "test-convo-1", messages: ["test:echo Third message in conversation 1"] }
];
// Send each message in each conversation
for (const conversation of testConversations) {
console.log(`\n--- Conversation: ${conversation.id} ---`);
for (const message of conversation.messages) {
// Call the agent proxy tool
const toolParams = {
name: "brightsy",
arguments: {
messages: [{ role: "user", content: message }],
conversationId: conversation.id
}
};
console.log(`\nSending message: "${message}"`);
console.log("Calling tool with params:", JSON.stringify(toolParams, null, 2));
const result = await client.callTool(toolParams);
console.log("Result:", JSON.stringify(result, null, 2));
}
}
// Test listing conversations
console.log("\n--- Testing list conversations command ---");
const listParams = {
name: "brightsy",
arguments: {
messages: [{ role: "user", content: "list conversations" }],
conversationId: "default"
}
};
console.log("Calling tool with params:", JSON.stringify(listParams, null, 2));
const listResult = await client.callTool(listParams);
console.log("Result:", JSON.stringify(listResult, null, 2));
// Test conversation stats
console.log("\n--- Testing conversation stats command ---");
const statsParams = {
name: "brightsy",
arguments: {
messages: [{ role: "user", content: "test:stats" }],
conversationId: "default"
}
};
console.log("Calling tool with params:", JSON.stringify(statsParams, null, 2));
const statsResult = await client.callTool(statsParams);
console.log("Result:", JSON.stringify(statsResult, null, 2));
} catch (error) {
console.error("Error:", error);
} finally {
// Clean up
console.log("\nCleaning up...");
try {
await client.close();
} catch (e) {
console.error("Error closing client:", e);
}
}
}
// Run the client
runClient().catch(error => {
console.error("Fatal error:", error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/src/test-direct.ts:
--------------------------------------------------------------------------------
```typescript
/**
* This script tests the agent_proxy tool directly by sending a properly formatted
* MCP request to the server's stdin and reading the response from stdout.
*/
import { spawn } from "child_process";
// Configuration
const agentId = process.env.AGENT_ID || "your-agent-id";
const apiKey = process.env.API_KEY || "your-api-key";
const toolName = process.env.TOOL_NAME || "brightsy";
console.log(`Starting direct test with agent ID: ${agentId}`);
console.log(`Using tool name: ${toolName}`);
// Check if command line arguments were provided
const args = process.argv.slice(2);
let cmdAgentId = agentId;
let cmdApiKey = apiKey;
let cmdToolName = toolName;
// Parse command line arguments
for (let i = 0; i < args.length; i++) {
const arg = args[i];
// Handle arguments with equals sign (--key=value)
if (arg.startsWith('--') && arg.includes('=')) {
const [key, value] = arg.substring(2).split('=', 2);
if (key === 'agent-id' || key === 'agent_id') {
cmdAgentId = value;
} else if (key === 'api-key' || key === 'api_key') {
cmdApiKey = value;
} else if (key === 'tool-name' || key === 'tool_name') {
cmdToolName = value;
}
}
// Handle arguments with space (--key value)
else if (arg.startsWith('--')) {
const key = arg.substring(2);
const nextArg = i + 1 < args.length ? args[i + 1] : undefined;
if (nextArg && !nextArg.startsWith('--')) {
if (key === 'agent-id' || key === 'agent_id') {
cmdAgentId = nextArg;
} else if (key === 'api-key' || key === 'api_key') {
cmdApiKey = nextArg;
} else if (key === 'tool-name' || key === 'tool_name') {
cmdToolName = nextArg;
}
i++; // Skip the next argument as we've used it as a value
}
}
// Handle positional arguments
else if (i === 0) {
cmdAgentId = arg;
} else if (i === 1) {
cmdApiKey = arg;
} else if (i === 2) {
cmdToolName = arg;
}
}
if (cmdAgentId !== agentId || cmdApiKey !== apiKey || cmdToolName !== toolName) {
console.log(`Using command line arguments: agent_id=${cmdAgentId}, tool_name=${cmdToolName}`);
}
// Spawn the MCP server process
const serverProcess = spawn("node", [
"dist/index.js",
cmdAgentId,
cmdApiKey,
cmdToolName
], {
cwd: process.cwd(),
stdio: ["pipe", "pipe", "inherit"] // We'll write to stdin and read from stdout
});
// Create a simple MCP request to call the tool
const mcpRequest = {
jsonrpc: "2.0",
id: "test-1",
method: "tools/call",
params: {
name: cmdToolName,
arguments: {
messages: [
{
role: "user",
content: "Hello, can you help me with a simple task?"
}
]
}
}
};
// Wait for the server to start up
setTimeout(() => {
console.log("Sending request to MCP server...");
console.log(`Using tool name in request: ${cmdToolName}`);
// Send the request to the server
serverProcess.stdin.write(JSON.stringify(mcpRequest) + "\n");
}, 2000);
// Process the response
let responseData = "";
serverProcess.stdout.on("data", (data) => {
responseData += data.toString();
try {
// Try to parse the response as JSON
const response = JSON.parse(responseData);
console.log("Received response:");
console.log(JSON.stringify(response, null, 2));
// Clean up and exit
serverProcess.kill();
process.exit(0);
} catch (error) {
// If we can't parse it yet, wait for more data
}
});
// Handle process events
serverProcess.on("error", (error) => {
console.error("Failed to start server process:", error);
process.exit(1);
});
serverProcess.on("exit", (code, signal) => {
if (code !== null && code !== 0) {
console.error(`Server process exited with code ${code}`);
process.exit(code);
}
});
// Handle termination signals
process.on("SIGINT", () => {
console.log("Received SIGINT, terminating server...");
serverProcess.kill("SIGINT");
process.exit(0);
});
// Set a timeout to prevent hanging
setTimeout(() => {
console.error("Test timed out after 30 seconds");
serverProcess.kill();
process.exit(1);
}, 30000);
```
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
```typescript
import { Client as McpClient } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { spawn, type ChildProcess } from "child_process";
import * as path from "path";
import * as fs from "fs";
import * as os from "os";
// Configuration from environment variables
const TEST_AGENT_ID = process.env.AGENT_ID || process.env.BRIGHTSY_AGENT_ID || '';
const TEST_API_KEY = process.env.API_KEY || process.env.BRIGHTSY_API_KEY || '';
const TOOL_NAME = process.env.TOOL_NAME || process.env.BRIGHTSY_TOOL_NAME || "brightsy";
// Validate required environment variables
if (!TEST_AGENT_ID || !TEST_API_KEY) {
console.error('Error: Required environment variables not set');
console.error('Please set the following environment variables:');
console.error(' AGENT_ID or BRIGHTSY_AGENT_ID: The agent ID to use for testing');
console.error(' API_KEY or BRIGHTSY_API_KEY: The API key to use for testing');
console.error(' TOOL_NAME or BRIGHTSY_TOOL_NAME: (optional) The tool name to use (default: brightsy)');
process.exit(1);
}
// After validation, we can safely assert these as strings
const validatedAgentId = TEST_AGENT_ID as string;
const validatedApiKey = TEST_API_KEY as string;
// Test cases
const testCases = [
{
name: "Create first message",
message: "test:echo First message",
expectedPartial: "```\nFirst message\n```"
},
{
name: "Add to conversation",
message: "test:echo Second message",
expectedPartial: "```\nSecond message\n```"
},
{
name: "Check conversation history",
message: "test:history",
expectedPartial: "test:echo First message"
},
{
name: "Clear conversation history",
message: "clear history",
expectedPartial: "history has been cleared"
},
{
name: "Verify history cleared",
message: "test:history",
expectedPartial: "I notice you want to test something related to history"
},
{
name: "Test simulation",
message: "test:simulate This is a simulated response",
expectedPartial: "simulated response"
}
];
async function runTests() {
console.log("Starting MCP stdio server tests...");
// Create a transport connected to stdio
const transport = new StdioClientTransport({
command: "/opt/homebrew/bin/node",
args: [
path.resolve("dist/index.js"),
"--agent-id", TEST_AGENT_ID,
"--api-key", TEST_API_KEY,
"--tool-name", TOOL_NAME
],
env: {
BRIGHTSY_TEST_MODE: "true"
}
});
// Create MCP client
const client = new McpClient({
name: "test-client",
version: "1.0.0"
});
try {
// Connect to the server
console.log("Connecting to server...");
await client.connect(transport);
console.log("Connected to server");
// Run each test case
let passedTests = 0;
for (const [index, test] of testCases.entries()) {
console.log(`\n[${index + 1}/${testCases.length}] Running test: ${test.name}`);
try {
// Call the agent proxy tool
const toolParams = {
name: TOOL_NAME,
arguments: {
messages: [{ role: "user", content: test.message }]
}
};
console.log("Calling tool with params:", JSON.stringify(toolParams, null, 2));
const result = await client.callTool(toolParams);
// Extract text from response
const responseText = (result.content as Array<{type: string, text: string}>)
.filter(item => item.type === "text")
.map(item => item.text)
.join("\n");
console.log(`Response: ${responseText}`);
// Check if response matches expected
let passed = false;
if (test.expectedPartial && responseText.includes(test.expectedPartial)) {
passed = true;
}
if (passed) {
console.log("✅ Test passed");
passedTests++;
} else {
console.log("❌ Test failed");
console.log(`Expected to include: ${test.expectedPartial}`);
console.log(`Actual: ${responseText}`);
}
} catch (error) {
console.error(`Error in test "${test.name}":`, error);
console.log("❌ Test failed due to error");
}
}
// Print summary
console.log(`\nTest summary: ${passedTests}/${testCases.length} tests passed`);
} catch (error) {
console.error("Error connecting to server:", error);
} finally {
// Clean up
console.log("Cleaning up...");
try {
await client.close();
} catch (e) {
console.error("Error closing client:", e);
}
}
}
// Run the tests
runTests().catch(error => {
console.error("Fatal error:", error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Get command line arguments
const args = process.argv.slice(2);
let agent_id: string | undefined = undefined;
let api_key: string | undefined = undefined;
let initialMessage: string | undefined = undefined;
let tool_name: string = process.env.BRIGHTSY_TOOL_NAME || "brightsy";
// Parse command-line arguments
for (let i = 0; i < args.length; i++) {
const arg = args[i];
// Handle arguments with equals sign (--key=value)
if (arg.startsWith('--') && arg.includes('=')) {
const [key, value] = arg.substring(2).split('=', 2);
if (key === 'agent-id' || key === 'agent_id') {
agent_id = value;
} else if (key === 'api-key' || key === 'api_key') {
api_key = value;
} else if (key === 'message') {
initialMessage = value;
} else if (key === 'tool-name' || key === 'tool_name') {
tool_name = value;
}
}
// Handle arguments with space (--key value)
else if (arg.startsWith('--')) {
const key = arg.substring(2);
const nextArg = i + 1 < args.length ? args[i + 1] : undefined;
if (nextArg && !nextArg.startsWith('--')) {
if (key === 'agent-id' || key === 'agent_id') {
agent_id = nextArg;
} else if (key === 'api-key' || key === 'api_key') {
api_key = nextArg;
} else if (key === 'message') {
initialMessage = nextArg;
} else if (key === 'tool-name' || key === 'tool_name') {
tool_name = nextArg;
}
i++; // Skip the next argument as we've used it as a value
}
}
// Handle positional arguments
else if (agent_id === undefined) {
agent_id = arg;
} else if (api_key === undefined) {
api_key = arg;
} else if (tool_name === undefined) {
tool_name = arg;
} else if (initialMessage === undefined) {
initialMessage = arg;
}
}
// Check for environment variables if not provided via command line
if (!agent_id) {
agent_id = process.env.BRIGHTSY_AGENT_ID;
}
if (!api_key) {
api_key = process.env.BRIGHTSY_API_KEY;
}
console.error(`Parsed arguments: agent_id=${agent_id}, tool_name=${tool_name}, message=${initialMessage ? 'provided' : 'not provided'}`);
if (!agent_id || !api_key) {
console.error('Usage: node dist/index.js <agent_id> <api_key> [tool_name] [message]');
console.error(' or: node dist/index.js --agent-id=<agent_id> --api-key=<api_key> [--tool-name=<tool_name>] [--message=<message>]');
console.error(' or: node dist/index.js --agent-id <agent_id> --api-key <api_key> [--tool-name <tool_name>] [--message <message>]');
console.error('');
console.error('Environment variables:');
console.error(' BRIGHTSY_AGENT_ID: Agent ID (alternative to command line argument)');
console.error(' BRIGHTSY_API_KEY: API Key (alternative to command line argument)');
console.error(' BRIGHTSY_TOOL_NAME: Tool name (default: brightsy)');
console.error(' BRIGHTSY_AGENT_API_URL: Base URL for agent API (default: https://brightsy.ai)');
process.exit(1);
}
// Create server instance
const server = new McpServer({
name: "brightsy-mcp",
version: "1.0.0",
});
// Add conversation history state to maintain session state
// This will store messages across multiple tool invocations
interface Message {
role: string;
content: string | any[];
}
// Initialize conversation history
let conversationHistory: Message[] = [];
// Get the agent API base URL from environment variable or use default
const agentApiBaseUrl = process.env.BRIGHTSY_AGENT_API_URL || 'https://brightsy.ai';
// Helper function to process content from the agent response
function processContent(content: any): { type: "text"; text: string }[] {
if (!content) {
return [{ type: "text", text: "No content in response" }];
}
// If content is a string, return it as text
if (typeof content === 'string') {
return [{ type: "text", text: content }];
}
// If content is an array, process each item
if (Array.isArray(content)) {
return content.map(item => {
if (typeof item === 'string') {
return { type: "text", text: item };
}
// Handle content blocks (text, image, etc.)
if (item.text) {
return { type: "text", text: item.text };
}
// For other types, convert to string representation
return {
type: "text",
text: `[${item.type} content: ${JSON.stringify(item)}]`
};
});
}
// If we can't process it, return a string representation
return [{ type: "text", text: JSON.stringify(content) }];
}
// Register the agent proxy tool
server.tool(
tool_name,
`Proxy requests to an Brightsy AI agent`,
{
messages: z.array(
z.object({
role: z.string().describe("The role of the message sender"),
content: z.union([z.string(), z.array(z.any())]).describe("The content of the message")
})
).describe("The messages to send to the agent")
},
async ({ messages }) => {
try {
console.error(`Agent proxy tool called with messages:`);
console.error(JSON.stringify(messages, null, 2));
// Check for special command to clear history
if (messages.length === 1 &&
messages[0].role === 'user' &&
typeof messages[0].content === 'string' &&
messages[0].content.trim().toLowerCase() === 'clear history') {
conversationHistory = [];
console.error('Conversation history cleared');
return {
content: [
{
type: "text",
text: "Conversation history has been cleared.",
},
],
};
}
// Check for test commands
if (messages.length === 1 &&
messages[0].role === 'user' &&
typeof messages[0].content === 'string') {
const content = messages[0].content.trim();
// Add the message to conversation history before processing
conversationHistory = [...conversationHistory, messages[0]];
// Handle test:echo command
if (content.startsWith('test:echo ')) {
const message = content.substring('test:echo '.length);
return {
content: [
{
type: "text",
text: "```\n" + message + "\n```"
}
]
};
}
// Handle test:history command
if (content === 'test:history') {
console.error(`Checking history with ${conversationHistory.length} messages:`);
conversationHistory.forEach((msg, i) => {
console.error(`[${i}] ${msg.role}: ${typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)}`);
});
if (conversationHistory.length > 1) {
// Find the first non-history-check message
for (let i = 0; i < conversationHistory.length - 1; i++) {
const msg = conversationHistory[i];
console.error(`Checking message ${i}: ${msg.role} - ${typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)}`);
if (msg.role === 'user' && typeof msg.content === 'string' && !msg.content.includes('test:history')) {
console.error(`Found message: ${msg.content}`);
return {
content: [
{
type: "text",
text: msg.content
}
]
};
}
}
}
console.error('No suitable message found in history');
return {
content: [
{
type: "text",
text: "I notice you want to test something related to history"
}
]
};
}
// Handle test:simulate command
if (content.startsWith('test:simulate ')) {
const message = content.substring('test:simulate '.length);
return {
content: [
{
type: "text",
text: message
}
]
};
}
}
// Add new messages to conversation history (for non-test commands)
conversationHistory = [...conversationHistory, ...messages];
console.error(`Using conversation history with ${conversationHistory.length} messages`);
const agentUrl = `${agentApiBaseUrl}/api/v1beta/agent/${agent_id}/chat/completion`;
console.error(`Forwarding request to agent: ${agent_id}`);
console.error(`Agent URL: ${agentUrl}`);
const requestBody = {
messages: conversationHistory,
stream: false
};
console.error(`Request body: ${JSON.stringify(requestBody, null, 2)}`);
const response = await fetch(agentUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${api_key}`
},
body: JSON.stringify(requestBody)
});
console.error(`Response status: ${response.status} ${response.statusText}`);
console.error(`Response headers: ${JSON.stringify(Object.fromEntries([...response.headers]), null, 2)}`);
// Clone the response to log the raw response body
const responseClone = response.clone();
const rawResponseText = await responseClone.text();
console.error(`Raw response body: ${rawResponseText}`);
if (!response.ok) {
const errorText = await response.text();
console.error(`Error from agent: ${response.status} ${errorText}`);
return {
content: [
{
type: "text",
text: `Error from agent: ${errorText}`,
},
],
};
}
// Try to parse the response as JSON
let data;
try {
data = JSON.parse(rawResponseText);
console.error(`Response received from agent and parsed as JSON`);
} catch (parseError) {
console.error(`Failed to parse response as JSON: ${parseError}`);
return {
content: [
{
type: "text",
text: `Error parsing response: ${parseError}\nRaw response: ${rawResponseText}`,
},
],
};
}
console.error(`Response data: ${JSON.stringify(data, null, 2)}`);
// Extract the assistant's response
const assistantMessage = data.choices?.[0]?.message;
if (!assistantMessage) {
console.error(`No assistant message found in response: ${JSON.stringify(data, null, 2)}`);
return {
content: [
{
type: "text",
text: "No message in agent response",
},
],
};
}
console.error(`Assistant message: ${JSON.stringify(assistantMessage, null, 2)}`);
// Add the assistant's response to the conversation history
if (assistantMessage) {
conversationHistory.push({
role: assistantMessage.role || 'assistant',
content: assistantMessage.content
});
console.error(`Added assistant response to history. History now has ${conversationHistory.length} messages`);
}
// Handle the case where content is already an array of content blocks
if (Array.isArray(assistantMessage.content)) {
console.error(`Content is an array, processing directly`);
// Map the content array to the expected format
const processedContent = assistantMessage.content.map((item: any) => {
if (typeof item === 'string') {
return { type: "text", text: item };
}
// Handle content blocks (text, image, etc.)
if (item.text) {
return { type: "text", text: item.text };
}
// For other types, convert to string representation
return {
type: "text",
text: `[${item.type || 'unknown'} content: ${JSON.stringify(item)}]`
};
});
console.error(`Directly processed content: ${JSON.stringify(processedContent, null, 2)}`);
return {
content: processedContent,
};
}
// Process the content from the assistant's message (for string or other formats)
const processedContent = processContent(assistantMessage.content);
console.error(`Processed content: ${JSON.stringify(processedContent, null, 2)}`);
return {
content: processedContent,
};
} catch (error) {
console.error('Error forwarding request:', error);
return {
content: [
{
type: "text",
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
},
);
// If an initial message was provided, process it immediately after server starts
async function processInitialMessage() {
if (initialMessage) {
console.error(`Processing initial message: ${initialMessage}`);
try {
// Create a messages array with the initial message
const messages = [
{ role: "user", content: initialMessage }
];
// Add to conversation history
conversationHistory.push(...messages);
// Instead of trying to access the tool directly, make a request to the local API
const agentUrl = `${agentApiBaseUrl}/api/v1beta/agent/${agent_id}/chat/completion`;
console.error(`Forwarding initial message to agent: ${agent_id}`);
const requestBody = {
messages: conversationHistory,
stream: false
};
const response = await fetch(agentUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${api_key}`
},
body: JSON.stringify(requestBody)
});
if (!response.ok) {
const errorText = await response.text();
console.error(`Error from agent: ${response.status} ${errorText}`);
console.log(`Error from agent: ${errorText}`);
return;
}
const data = await response.json();
console.error(`Response received from agent`);
// Extract the assistant's response
const assistantMessage = data.choices?.[0]?.message;
if (!assistantMessage) {
console.log("No message in agent response");
return;
}
// Add the assistant's response to the conversation history
if (assistantMessage) {
conversationHistory.push({
role: assistantMessage.role || 'assistant',
content: assistantMessage.content
});
console.error(`Added assistant response to history. History now has ${conversationHistory.length} messages`);
}
// Handle the case where content is already an array of content blocks
if (Array.isArray(assistantMessage.content)) {
// Map the content array to extract text
const textOutput = assistantMessage.content
.map((item: any) => {
if (typeof item === 'string') return item;
if (item.text) return item.text;
return JSON.stringify(item);
})
.join("\n");
console.log(textOutput);
return;
}
// Process and output the content
const processedContent = processContent(assistantMessage.content);
const textOutput = processedContent
.filter(item => item.type === "text")
.map(item => item.text)
.join("\n");
console.log(textOutput);
} catch (error) {
console.error("Error processing initial message:", error);
console.log(`Error: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
// Start the server
async function main() {
try {
const transport = new StdioServerTransport();
// Add event listeners for process events
process.on('SIGINT', () => {
console.error('Received SIGINT signal, shutting down...');
process.exit(0);
});
process.on('SIGTERM', () => {
console.error('Received SIGTERM signal, shutting down...');
process.exit(0);
});
// Keep stdin open
process.stdin.resume();
// Connect to the transport
console.error(`Connecting to transport...`);
await server.connect(transport);
console.error(`Brightsy MCP Server running on stdio`);
console.error(`Connected to agent: ${agent_id}`);
console.error(`Registered tool name: ${tool_name}`);
console.error(`Agent API URL: ${agentApiBaseUrl}`);
console.error(`Ready to receive requests`);
// Process initial message if provided
await processInitialMessage();
} catch (error) {
console.error("Error starting server:", error);
process.exit(1);
}
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
```