Add experimental MCP server

This commit is contained in:
momo5502
2025-06-08 13:51:45 +02:00
parent 7fde3dadab
commit 48170b6e41
5 changed files with 623 additions and 0 deletions

41
mcp/.gitignore vendored Normal file
View File

@@ -0,0 +1,41 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
*.lcov
# nyc test coverage
.nyc_output
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Editor directories and files
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

118
mcp/README.md Normal file
View File

@@ -0,0 +1,118 @@
# Sogen MCP Server
A Model Context Protocol (MCP) server that provides AI access to the sogen userspace emulator.
## Setup
1. Install dependencies:
```bash
npm install
```
2. Run the server:
```bash
npm start
```
Or for development with auto-restart:
```bash
npm run dev
```
## Structure
- `server.js` - Main server implementation
- `package.json` - Node.js project configuration
## Available Tools
### `list_applications`
Lists all available applications in the sogen userspace emulator.
**Parameters:** None
**Example usage:**
```json
{
"name": "list_applications",
"arguments": {}
}
```
### `run_application`
Executes a specific application from the allowed list in the sogen userspace emulator.
**Parameters:**
- `application` (string, required): The name of the application to run (use `list_applications` to see available apps)
- `args` (array of strings, optional): Arguments to pass to the application
- `timeout` (number, optional): Timeout in milliseconds (default: 5000)
**Example usage:**
```json
{
"name": "run_application",
"arguments": {
"application": "echo",
"args": ["Hello from sogen!"],
"timeout": 3000
}
}
```
## Adding More Tools
To add additional tools:
1. Add the tool definition to the `ListToolsRequestSchema` handler
2. Add the implementation to the `CallToolRequestSchema` handler
3. Create the corresponding method in the `MyMCPServer` class
## Execution Model
The server uses an **analyzer-based execution model**:
- Applications are not executed directly
- Instead, the server runs: `C:/analyer.exe -e C:/somedir <application_name> [args...]`
- The analyzer handles the actual execution within the sogen environment
- All output comes from the analyzer process
### Command Structure
```
C:/analyer.exe -e C:/somedir <application_name> [arguments...]
```
Where:
- `C:/analyer.exe` - The sogen analyzer executable
- `-e C:/somedir` - Analyzer flags and environment directory
- `<application_name>` - The application from `get_applications()`
- `[arguments...]` - Optional arguments passed to the application
## Implementation Required
You need to provide the implementation for the `get_applications()` method in `server.js`. This method should:
```javascript
async get_applications() {
// Return a Promise that resolves to a string array
// Example: return ['echo', 'ls', 'cat', 'grep'];
}
```
## Usage
This server allows AI assistants to:
1. **List available applications** using `list_applications`
2. **Execute specific applications** using `run_application` with validation
The server communicates over stdio and is designed for MCP-compatible clients.
### Example Workflow
1. Call `list_applications` to see what's available
2. Call `run_application` with a valid application name and arguments
## Next Steps
1. **Implement `get_applications()` method** - Provide the actual implementation
2. Add application-specific argument validation
3. Implement resource handling for file access
4. Add comprehensive logging and monitoring
5. Consider per-application sandboxing for enhanced security

151
mcp/package-lock.json generated Normal file
View File

@@ -0,0 +1,151 @@
{
"name": "my-mcp-server",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "my-mcp-server",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^0.5.0"
}
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.5.0.tgz",
"integrity": "sha512-RXgulUX6ewvxjAG0kOpLMEdXXWkzWgaoCGaA2CwNW7cQCIphjpJhjpHSiaPdVCnisjRF/0Cm9KWHUuIoeiAblQ==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"raw-body": "^3.0.0",
"zod": "^3.23.8"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/raw-body": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.6.3",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/zod": {
"version": "3.25.56",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.56.tgz",
"integrity": "sha512-rd6eEF3BTNvQnR2e2wwolfTmUTnp70aUTqr0oaGbHifzC3BKJsoV+Gat8vxUMR1hwOKBs6El+qWehrHbCpW6SQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

17
mcp/package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "sogen-mcp-server",
"version": "1.0.0",
"description": "MCP server for sogen userspace emulator",
"main": "server.js",
"type": "module",
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^0.5.0"
},
"keywords": ["mcp", "server"],
"author": "",
"license": "MIT"
}

296
mcp/server.js Normal file
View File

@@ -0,0 +1,296 @@
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { spawn } from "child_process";
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
class SogenMCPServer {
constructor() {
this.server = new Server(
{
name: "sogen-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
this.setupErrorHandling();
}
setupToolHandlers() {
// Handle tool listing
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "list_applications",
description:
"List all available applications in the sogen userspace emulator",
inputSchema: {
type: "object",
properties: {},
additionalProperties: false,
},
},
{
name: "run_application",
description:
"Execute a specific application in the sogen userspace emulator",
inputSchema: {
type: "object",
properties: {
application: {
type: "string",
description:
"The name of the application to run (use list_applications to see available apps)",
},
args: {
type: "array",
items: {
type: "string",
},
description: "Arguments to pass to the application",
default: [],
},
timeout: {
type: "number",
description: "Timeout in milliseconds (default: 50000)",
default: 50000,
},
},
required: ["application"],
},
},
],
};
});
// Handle tool execution
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "list_applications":
return await this.listApplications();
case "run_application":
return await this.runApplication(
args.application,
args.args || [],
args.timeout || 50000
);
default:
throw new Error(`Unknown tool: ${name}`);
}
});
}
async listApplications() {
try {
const applications = await this.get_applications();
return {
content: [
{
type: "text",
text: `Available applications in sogen:\n\n${applications
.map((app) => `${app}`)
.join("\n")}\n\nTotal: ${applications.length} application(s)`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error listing applications: ${error.message}`,
},
],
};
}
}
async runApplication(applicationName, args = [], timeout = 5000) {
try {
// First, get the list of available applications to validate
const availableApps = await this.get_applications();
if (!availableApps.includes(applicationName)) {
return {
content: [
{
type: "text",
text: `Error: Application '${applicationName}' is not available.\n\nAvailable applications:\n${availableApps
.map((app) => `${app}`)
.join("\n")}`,
},
],
};
}
return await this.executeApplication(applicationName, args, timeout);
} catch (error) {
return {
content: [
{
type: "text",
text: `Error running application '${applicationName}': ${error.message}`,
},
],
};
}
}
async executeApplication(applicationName, args, timeout) {
return new Promise((resolve, reject) => {
// Get the parent directory (emulator root)
const emulatorRoot = path.dirname(__dirname);
// Build the analyzer command: C:/analyer.exe -e C:/somedir <application_name>
const analyzerPath =
"C:\\Users\\mauri\\Desktop\\emulator\\build\\vs2022\\artifacts-relwithdebinfo\\analyzer.exe";
const analyzerArgs = [
"-c",
"-e",
"C:\\Users\\mauri\\Desktop\\emulator\\src\\tools\\root",
applicationName,
...args,
];
const child = spawn(analyzerPath, analyzerArgs, {
cwd: "C:\\Users\\mauri\\Desktop\\emulator\\build\\vs2022\\artifacts-relwithdebinfo",
shell: true,
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
let timedOut = false;
// Set up timeout
const timer = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
}, timeout);
// Collect stdout
child.stdout.on("data", (data) => {
stdout += data.toString();
});
// Collect stderr
child.stderr.on("data", (data) => {
stderr += data.toString();
});
// Handle process completion
child.on("close", (code) => {
clearTimeout(timer);
if (timedOut) {
resolve({
content: [
{
type: "text",
text: `Application '${applicationName}' timed out after ${timeout}ms\nAnalyzer command: ${analyzerPath} ${analyzerArgs.join(
" "
)}\nPartial stdout: ${stdout}\nPartial stderr: ${stderr}`,
},
],
});
} else {
const output = [];
if (stdout) {
output.push(`STDOUT:\n${stdout}`);
}
if (stderr) {
output.push(`STDERR:\n${stderr}`);
}
if (!stdout && !stderr) {
output.push("Application executed successfully with no output.");
}
output.push(`Exit code: ${code}`);
resolve({
content: [
{
type: "text",
text: `Application: ${applicationName}\nArguments: [${args.join(
", "
)}]\nAnalyzer command: ${analyzerPath} ${analyzerArgs.join(
" "
)}\n\n${output.join("\n\n")}`,
},
],
});
}
});
// Handle spawn errors
child.on("error", (error) => {
clearTimeout(timer);
resolve({
content: [
{
type: "text",
text: `Error executing application '${applicationName}' via analyzer: ${
error.message
}\nAnalyzer command: ${analyzerPath} ${analyzerArgs.join(" ")}`,
},
],
});
});
});
}
async get_applications() {
return [
"c:/test-sample.exe",
"c:/wukong/b1/Binaries/Win64/b1-Win64-Shipping.exe",
];
}
setupErrorHandling() {
this.server.onerror = (error) => {
console.error("[MCP Error]", error);
};
process.on("SIGINT", async () => {
await this.server.close();
process.exit(0);
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("MCP Server running on stdio");
}
}
// Start the server
const server = new SogenMCPServer();
server.run().catch((error) => {
console.error("Failed to start server:", error);
process.exit(1);
});