mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-11 16:46:16 +00:00
Add experimental MCP server
This commit is contained in:
41
mcp/.gitignore
vendored
Normal file
41
mcp/.gitignore
vendored
Normal 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
118
mcp/README.md
Normal 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
151
mcp/package-lock.json
generated
Normal 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
17
mcp/package.json
Normal 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
296
mcp/server.js
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user