#
tokens: 11079/50000 13/13 files
lines: off (toggle) GitHub
raw markdown copy
# 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);
}); 
```