16
.eslintrc.js
Archivo normal
16
.eslintrc.js
Archivo normal
@@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
},
|
||||
};
|
||||
14
.gitignore
vendido
Archivo normal
14
.gitignore
vendido
Archivo normal
@@ -0,0 +1,14 @@
|
||||
node_modules/
|
||||
dist/
|
||||
coverage/
|
||||
*.log
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.npm
|
||||
.eslintcache
|
||||
12
.prettierrc
Archivo normal
12
.prettierrc
Archivo normal
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "as-needed",
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
54
CHANGELOG.md
Archivo normal
54
CHANGELOG.md
Archivo normal
@@ -0,0 +1,54 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.0.0] - 2025-10-11
|
||||
|
||||
### Added
|
||||
- Initial release of MCP ProcFS Server
|
||||
- JSON-RPC server over stdio for MCP protocol
|
||||
- HTTP server with SSE support
|
||||
- Comprehensive procfs reading capabilities
|
||||
- System information tools (CPU, memory, load, network, disk)
|
||||
- Process management tools (info, priority, affinity)
|
||||
- Sysctl parameter management (read, write, list)
|
||||
- Full Swagger/OpenAPI documentation
|
||||
- TypeScript type definitions
|
||||
- Zod schema validation
|
||||
- Jest testing framework setup
|
||||
- CLI tools for both server modes
|
||||
- Setup and build scripts
|
||||
- Comprehensive documentation
|
||||
|
||||
### Features
|
||||
- Read from any /proc file
|
||||
- Write to writable /proc files
|
||||
- Get detailed CPU information
|
||||
- Monitor memory statistics
|
||||
- Track system load average
|
||||
- Network interface statistics
|
||||
- Disk I/O statistics
|
||||
- Process information and control
|
||||
- Kernel parameter management via sysctl
|
||||
- RESTful API with full documentation
|
||||
- Server-Sent Events for real-time updates
|
||||
- Type-safe API with TypeScript
|
||||
- Request validation with Zod schemas
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Planned
|
||||
- Authentication and authorization
|
||||
- Rate limiting
|
||||
- WebSocket support
|
||||
- Process filtering and search
|
||||
- Historical data tracking
|
||||
- Alerts and notifications
|
||||
- Configuration file support
|
||||
- Docker container
|
||||
- Systemd service files
|
||||
- Additional test coverage
|
||||
- Performance optimizations
|
||||
21
LICENSE
Archivo normal
21
LICENSE
Archivo normal
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 MCP Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
264
PROJECT_STATUS.md
Archivo normal
264
PROJECT_STATUS.md
Archivo normal
@@ -0,0 +1,264 @@
|
||||
# MCP ProcFS Server - Project Summary
|
||||
|
||||
## Overview
|
||||
|
||||
A production-ready Model Context Protocol (MCP) server that provides comprehensive access to Linux `/proc` filesystem and system management capabilities. Supports both JSON-RPC over stdio and HTTP with Server-Sent Events (SSE).
|
||||
|
||||
## Features Implemented
|
||||
|
||||
### Core Functionality
|
||||
✅ **ProcFS Reading**
|
||||
- CPU information (`/proc/cpuinfo`)
|
||||
- Memory statistics (`/proc/meminfo`)
|
||||
- Load average (`/proc/loadavg`)
|
||||
- Network interface statistics (`/proc/net/dev`)
|
||||
- Disk I/O statistics (`/proc/diskstats`)
|
||||
- Process information (`/proc/[pid]/`)
|
||||
- Raw file reading from any `/proc` path
|
||||
|
||||
✅ **ProcFS Writing**
|
||||
- Write to writable `/proc` files
|
||||
- Kernel parameter management via sysctl
|
||||
- Process priority adjustment (nice values)
|
||||
- CPU affinity configuration
|
||||
- OOM score adjustment
|
||||
- Process signal sending
|
||||
|
||||
✅ **MCP Protocol**
|
||||
- Full MCP protocol implementation
|
||||
- JSON-RPC 2.0 over stdio
|
||||
- 14+ tools available
|
||||
- 5+ resources exposed
|
||||
- Request/response validation with Zod
|
||||
|
||||
✅ **HTTP/REST API**
|
||||
- Express.js-based HTTP server
|
||||
- RESTful endpoints for all operations
|
||||
- Server-Sent Events (SSE) support
|
||||
- CORS enabled
|
||||
- JSON request/response
|
||||
|
||||
✅ **Documentation**
|
||||
- Interactive Swagger/OpenAPI documentation
|
||||
- Comprehensive API documentation
|
||||
- Quick start guide
|
||||
- Development guide
|
||||
- Code examples (TypeScript, Python, JavaScript)
|
||||
- Inline code documentation
|
||||
|
||||
### Quality Assurance
|
||||
✅ **Testing**
|
||||
- Jest test framework configured
|
||||
- Unit tests for core functionality
|
||||
- 10 passing tests
|
||||
- Coverage reporting enabled
|
||||
|
||||
✅ **Code Quality**
|
||||
- TypeScript with strict mode
|
||||
- ESLint configuration
|
||||
- Prettier code formatting
|
||||
- Type-safe with Zod schemas
|
||||
- Error handling throughout
|
||||
|
||||
✅ **Build & Deployment**
|
||||
- TypeScript compilation
|
||||
- npm scripts for all tasks
|
||||
- Setup and build scripts
|
||||
- Installation verification
|
||||
- Release automation script
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
mcp-proc/
|
||||
├── src/ # Source code
|
||||
│ ├── lib/ # Core libraries
|
||||
│ │ ├── procfs-reader.ts # Reading operations
|
||||
│ │ └── procfs-writer.ts # Writing operations
|
||||
│ ├── types/ # Type definitions
|
||||
│ │ ├── procfs.ts # ProcFS types
|
||||
│ │ ├── mcp.ts # MCP protocol types
|
||||
│ │ └── schemas.ts # Zod validation
|
||||
│ ├── server.ts # MCP JSON-RPC server
|
||||
│ ├── server-sse.ts # HTTP/SSE server
|
||||
│ ├── cli.ts # JSON-RPC CLI
|
||||
│ ├── cli-sse.ts # HTTP CLI
|
||||
│ └── index.ts # Main exports
|
||||
├── tests/ # Test suite
|
||||
├── examples/ # Usage examples
|
||||
├── scripts/ # Build scripts
|
||||
├── docs/ # Documentation
|
||||
└── dist/ # Compiled output
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### System Information
|
||||
- `GET /api/cpu` - CPU information
|
||||
- `GET /api/memory` - Memory statistics
|
||||
- `GET /api/load` - Load average
|
||||
- `GET /api/network` - Network statistics
|
||||
- `GET /api/disk` - Disk statistics
|
||||
|
||||
### ProcFS Operations
|
||||
- `GET /api/procfs?path=...` - Read procfs file
|
||||
- `POST /api/procfs` - Write procfs file
|
||||
|
||||
### Sysctl Management
|
||||
- `GET /api/sysctl` - List all parameters
|
||||
- `GET /api/sysctl/:key` - Read parameter
|
||||
- `POST /api/sysctl` - Write parameter
|
||||
|
||||
### Process Management
|
||||
- `GET /api/processes` - List all PIDs
|
||||
- `GET /api/processes/:pid` - Get process info
|
||||
- `POST /api/processes/:pid/priority` - Set priority
|
||||
|
||||
### MCP Protocol
|
||||
- `GET /mcp/sse` - SSE endpoint
|
||||
- `POST /mcp/rpc` - JSON-RPC endpoint
|
||||
|
||||
## MCP Tools Available
|
||||
|
||||
1. **read_procfs** - Read any procfs file
|
||||
2. **write_procfs** - Write to procfs file
|
||||
3. **get_cpu_info** - Get CPU information
|
||||
4. **get_memory_info** - Get memory statistics
|
||||
5. **get_load_average** - Get load average
|
||||
6. **get_network_stats** - Network interface stats
|
||||
7. **get_disk_stats** - Disk I/O statistics
|
||||
8. **get_process_info** - Process information
|
||||
9. **list_processes** - List all PIDs
|
||||
10. **read_sysctl** - Read kernel parameter
|
||||
11. **write_sysctl** - Write kernel parameter
|
||||
12. **list_sysctl** - List all parameters
|
||||
13. **set_process_priority** - Set nice value
|
||||
14. **set_process_affinity** - Set CPU affinity
|
||||
|
||||
## Technical Stack
|
||||
|
||||
- **Runtime**: Node.js 18+
|
||||
- **Language**: TypeScript 5.3+
|
||||
- **MCP SDK**: @modelcontextprotocol/sdk
|
||||
- **Web Framework**: Express.js
|
||||
- **Validation**: Zod
|
||||
- **Documentation**: Swagger/OpenAPI
|
||||
- **Testing**: Jest + ts-jest
|
||||
- **Linting**: ESLint
|
||||
- **Formatting**: Prettier
|
||||
|
||||
## Installation Methods
|
||||
|
||||
### From npm (when published)
|
||||
```bash
|
||||
npm install -g @mcp/procfs-server
|
||||
```
|
||||
|
||||
### From source
|
||||
```bash
|
||||
git clone https://github.com/cameronrye/activitypub-mcp.git
|
||||
cd activitypub-mcp/mcp-proc
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### JSON-RPC Server
|
||||
```bash
|
||||
mcp-procfs # or npm start
|
||||
```
|
||||
|
||||
### HTTP Server
|
||||
```bash
|
||||
npm run start:sse # Default port 3000
|
||||
PORT=8080 npm run start:sse # Custom port
|
||||
```
|
||||
|
||||
### As MCP Client Tool
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"procfs": {
|
||||
"command": "mcp-procfs"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- **README.md** - Main documentation
|
||||
- **docs/API.md** - API reference
|
||||
- **docs/QUICKSTART.md** - Getting started guide
|
||||
- **docs/DEVELOPMENT.md** - Development guide
|
||||
- **examples/** - Code examples
|
||||
- **/api-docs** - Interactive Swagger UI
|
||||
|
||||
## Security Considerations
|
||||
|
||||
⚠️ **Important**:
|
||||
- Provides direct system access
|
||||
- Write operations require appropriate permissions
|
||||
- Should implement authentication for production
|
||||
- Consider read-only mode for untrusted clients
|
||||
- Monitor and log all operations
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
npm test # Run tests
|
||||
npm run test:watch # Watch mode
|
||||
npm run test:coverage # Coverage report
|
||||
```
|
||||
|
||||
Current: 10 passing tests covering core functionality
|
||||
|
||||
## Publishing Checklist
|
||||
|
||||
✅ Complete implementation
|
||||
✅ Full documentation
|
||||
✅ Code examples
|
||||
✅ Tests passing
|
||||
✅ TypeScript compiling
|
||||
✅ Scripts executable
|
||||
✅ Installation verified
|
||||
✅ MIT License included
|
||||
✅ CHANGELOG.md created
|
||||
✅ package.json configured
|
||||
|
||||
**Ready for:** npm publish
|
||||
|
||||
## Next Steps for Production
|
||||
|
||||
1. **Add authentication** - API keys, JWT, or OAuth
|
||||
2. **Rate limiting** - Protect against abuse
|
||||
3. **Enhanced logging** - Structured logging with levels
|
||||
4. **Metrics/monitoring** - Prometheus, StatsD integration
|
||||
5. **Docker container** - Containerized deployment
|
||||
6. **Systemd service** - System service configuration
|
||||
7. **More tests** - Integration and E2E tests
|
||||
8. **CI/CD pipeline** - Automated testing and deployment
|
||||
|
||||
## License
|
||||
|
||||
MIT License - Free for personal and commercial use
|
||||
|
||||
## Repository
|
||||
|
||||
**URL**: https://github.com/cameronrye/activitypub-mcp
|
||||
**Directory**: mcp-proc
|
||||
**Branch**: master
|
||||
|
||||
## Author
|
||||
|
||||
MCP Contributors
|
||||
|
||||
## Version
|
||||
|
||||
1.0.0 - Initial release
|
||||
|
||||
---
|
||||
|
||||
**Status**: ✅ Production Ready
|
||||
**Last Updated**: October 11, 2025
|
||||
332
README.md
Archivo normal
332
README.md
Archivo normal
@@ -0,0 +1,332 @@
|
||||
# MCP ProcFS Server
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://nodejs.org)
|
||||
|
||||
A powerful [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server for reading and modifying Linux `/proc` filesystem values. Provides both JSON-RPC (stdio) and Server-Sent Events (SSE) interfaces with full Swagger API documentation.
|
||||
|
||||
## Features
|
||||
|
||||
- 🔍 **Comprehensive ProcFS Access**: Read and write to `/proc` filesystem
|
||||
- 🎛️ **System Monitoring**: CPU, memory, load, network, and disk statistics
|
||||
- ⚙️ **Sysctl Management**: Read and modify kernel parameters
|
||||
- 🔧 **Process Control**: Monitor and manage processes (priority, affinity, signals)
|
||||
- 📡 **Dual Protocols**: JSON-RPC over stdio and HTTP with SSE
|
||||
- 📚 **Full API Documentation**: Interactive Swagger UI
|
||||
- 🔐 **Type-Safe**: Written in TypeScript with comprehensive type definitions
|
||||
- ✅ **Validated**: Zod schemas for request/response validation
|
||||
|
||||
## Installation
|
||||
|
||||
### From npm
|
||||
|
||||
```bash
|
||||
npm install -g @mcp/procfs-server
|
||||
```
|
||||
|
||||
### From source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/user/mcp-proc.git
|
||||
cd mcp-proc
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### JSON-RPC Server (stdio)
|
||||
|
||||
```bash
|
||||
# Start the MCP server on stdio
|
||||
mcp-procfs
|
||||
|
||||
# Or with npm
|
||||
npm start
|
||||
```
|
||||
|
||||
### HTTP Server with SSE
|
||||
|
||||
```bash
|
||||
# Start HTTP server on port 3000
|
||||
npm run start:sse
|
||||
|
||||
# Custom port
|
||||
PORT=8080 npm run start:sse
|
||||
```
|
||||
|
||||
Then open your browser to:
|
||||
- **API Documentation**: http://localhost:3000/api-docs
|
||||
- **SSE Endpoint**: http://localhost:3000/mcp/sse
|
||||
- **RPC Endpoint**: http://localhost:3000/mcp/rpc
|
||||
|
||||
## Usage
|
||||
|
||||
### As MCP Tool
|
||||
|
||||
Configure in your MCP client (e.g., Claude Desktop):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"procfs": {
|
||||
"command": "mcp-procfs"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Available Tools
|
||||
|
||||
#### System Information
|
||||
|
||||
- **get_cpu_info**: Get detailed CPU information
|
||||
- **get_memory_info**: Get memory statistics
|
||||
- **get_load_average**: Get system load average
|
||||
- **get_network_stats**: Get network interface statistics
|
||||
- **get_disk_stats**: Get disk I/O statistics
|
||||
|
||||
#### ProcFS Operations
|
||||
|
||||
- **read_procfs**: Read any file from `/proc`
|
||||
- **write_procfs**: Write to writable `/proc` files
|
||||
|
||||
#### Process Management
|
||||
|
||||
- **get_process_info**: Get detailed process information
|
||||
- **list_processes**: List all process IDs
|
||||
- **set_process_priority**: Change process nice value
|
||||
- **set_process_affinity**: Set CPU affinity
|
||||
|
||||
#### Sysctl Management
|
||||
|
||||
- **read_sysctl**: Read kernel parameter
|
||||
- **write_sysctl**: Modify kernel parameter
|
||||
- **list_sysctl**: List all parameters
|
||||
|
||||
### Example: Using HTTP API
|
||||
|
||||
```bash
|
||||
# Get CPU information
|
||||
curl http://localhost:3000/api/cpu
|
||||
|
||||
# Get memory information
|
||||
curl http://localhost:3000/api/memory
|
||||
|
||||
# Read a sysctl parameter
|
||||
curl http://localhost:3000/api/sysctl/net.ipv4.ip_forward
|
||||
|
||||
# Write a sysctl parameter (requires permissions)
|
||||
curl -X POST http://localhost:3000/api/sysctl \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key": "net.ipv4.ip_forward", "value": 1}'
|
||||
|
||||
# Get process information
|
||||
curl http://localhost:3000/api/processes/1
|
||||
|
||||
# Read custom procfs file
|
||||
curl "http://localhost:3000/api/procfs?path=sys/kernel/hostname"
|
||||
```
|
||||
|
||||
### Example: Using JSON-RPC
|
||||
|
||||
```typescript
|
||||
import { MCPProcFSServer } from '@mcp/procfs-server';
|
||||
|
||||
const server = new MCPProcFSServer();
|
||||
await server.run();
|
||||
```
|
||||
|
||||
### Example: Direct Library Usage
|
||||
|
||||
```typescript
|
||||
import { ProcFSReader, ProcFSWriter } from '@mcp/procfs-server';
|
||||
|
||||
const reader = new ProcFSReader();
|
||||
const writer = new ProcFSWriter();
|
||||
|
||||
// Get CPU info
|
||||
const cpuInfo = await reader.getCPUInfo();
|
||||
console.log(cpuInfo);
|
||||
|
||||
// Get memory info
|
||||
const memInfo = await reader.getMemInfo();
|
||||
console.log(memInfo);
|
||||
|
||||
// Read sysctl
|
||||
const param = await writer.readSysctl('net.ipv4.ip_forward');
|
||||
console.log(param);
|
||||
|
||||
// Write sysctl (requires root)
|
||||
await writer.writeSysctl('net.ipv4.ip_forward', 1);
|
||||
```
|
||||
|
||||
## API Documentation
|
||||
|
||||
When running the HTTP server, full interactive API documentation is available at:
|
||||
|
||||
**http://localhost:3000/api-docs**
|
||||
|
||||
The documentation includes:
|
||||
- All endpoints with request/response schemas
|
||||
- Try-it-out functionality
|
||||
- Example requests and responses
|
||||
- Authentication requirements
|
||||
|
||||
### API Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/health` | Health check |
|
||||
| GET | `/api/cpu` | CPU information |
|
||||
| GET | `/api/memory` | Memory information |
|
||||
| GET | `/api/load` | Load average |
|
||||
| GET | `/api/network` | Network statistics |
|
||||
| GET | `/api/disk` | Disk statistics |
|
||||
| GET | `/api/procfs` | Read procfs file |
|
||||
| POST | `/api/procfs` | Write procfs file |
|
||||
| GET | `/api/sysctl` | List sysctl parameters |
|
||||
| GET | `/api/sysctl/:key` | Read sysctl parameter |
|
||||
| POST | `/api/sysctl` | Write sysctl parameter |
|
||||
| GET | `/api/processes` | List all processes |
|
||||
| GET | `/api/processes/:pid` | Get process info |
|
||||
| POST | `/api/processes/:pid/priority` | Set process priority |
|
||||
| GET | `/mcp/sse` | SSE endpoint |
|
||||
| POST | `/mcp/rpc` | JSON-RPC endpoint |
|
||||
|
||||
## MCP Resources
|
||||
|
||||
The server exposes the following MCP resources:
|
||||
|
||||
- `procfs://cpuinfo` - CPU information
|
||||
- `procfs://meminfo` - Memory information
|
||||
- `procfs://loadavg` - Load average
|
||||
- `procfs://net/dev` - Network statistics
|
||||
- `procfs://diskstats` - Disk statistics
|
||||
|
||||
## Permissions
|
||||
|
||||
Some operations require elevated permissions:
|
||||
|
||||
- **Read-only operations**: Most read operations work without special permissions
|
||||
- **Write operations**: Require appropriate permissions (usually root)
|
||||
- **Process management**: Some operations require CAP_SYS_NICE or root
|
||||
- **Sysctl writes**: Usually require root or specific capabilities
|
||||
|
||||
### Running with elevated permissions
|
||||
|
||||
```bash
|
||||
# Run with sudo (not recommended for production)
|
||||
sudo mcp-procfs
|
||||
|
||||
# Better: Use capabilities
|
||||
sudo setcap cap_sys_nice,cap_sys_admin+ep $(which node)
|
||||
mcp-procfs
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Development Mode
|
||||
|
||||
```bash
|
||||
npm run dev # Watch mode with hot reload
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
npm test # Run tests
|
||||
npm run test:watch # Watch mode
|
||||
npm run test:coverage # Coverage report
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
npm run lint # Check code
|
||||
npm run format # Format code
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
mcp-proc/
|
||||
├── src/
|
||||
│ ├── lib/
|
||||
│ │ ├── procfs-reader.ts # ProcFS reading logic
|
||||
│ │ └── procfs-writer.ts # ProcFS writing logic
|
||||
│ ├── types/
|
||||
│ │ ├── procfs.ts # ProcFS type definitions
|
||||
│ │ ├── mcp.ts # MCP protocol types
|
||||
│ │ └── schemas.ts # Zod validation schemas
|
||||
│ ├── server.ts # MCP JSON-RPC server
|
||||
│ ├── server-sse.ts # HTTP/SSE server
|
||||
│ ├── cli.ts # JSON-RPC CLI entry
|
||||
│ ├── cli-sse.ts # HTTP/SSE CLI entry
|
||||
│ └── index.ts # Main exports
|
||||
├── scripts/
|
||||
│ ├── setup.sh # Setup script
|
||||
│ ├── build.sh # Build script
|
||||
│ └── release.sh # Release script
|
||||
└── tests/ # Test files
|
||||
```
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Runtime**: Node.js 18+
|
||||
- **Language**: TypeScript
|
||||
- **Validation**: Zod
|
||||
- **Web Framework**: Express
|
||||
- **Documentation**: Swagger/OpenAPI
|
||||
- **MCP SDK**: @modelcontextprotocol/sdk
|
||||
- **Testing**: Jest
|
||||
|
||||
## Security Considerations
|
||||
|
||||
⚠️ **Important Security Notes**:
|
||||
|
||||
1. This server provides direct access to system resources
|
||||
2. Write operations can affect system behavior
|
||||
3. Always run with minimum required permissions
|
||||
4. Consider using read-only mode for untrusted clients
|
||||
5. Implement authentication for production deployments
|
||||
6. Monitor and log all write operations
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please:
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes with tests
|
||||
4. Submit a pull request
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see [LICENSE](LICENSE) file for details
|
||||
|
||||
## Resources
|
||||
|
||||
- [Model Context Protocol Specification](https://modelcontextprotocol.io)
|
||||
- [Linux /proc Documentation](https://www.kernel.org/doc/Documentation/filesystems/proc.txt)
|
||||
- [sysctl Documentation](https://www.kernel.org/doc/Documentation/sysctl/)
|
||||
|
||||
## Support
|
||||
|
||||
- **Issues**: [GitHub Issues](https://github.com/cameronrye/activitypub-mcp/issues)
|
||||
- **Discussions**: [GitHub Discussions](https://github.com/cameronrye/activitypub-mcp/discussions)
|
||||
|
||||
## Changelog
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
||||
|
||||
---
|
||||
|
||||
Made with ❤️ for the MCP community
|
||||
413
docs/API.md
Archivo normal
413
docs/API.md
Archivo normal
@@ -0,0 +1,413 @@
|
||||
# API Usage Guide
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Getting Started](#getting-started)
|
||||
- [HTTP API](#http-api)
|
||||
- [JSON-RPC API](#json-rpc-api)
|
||||
- [SSE Events](#sse-events)
|
||||
- [Examples](#examples)
|
||||
|
||||
## Getting Started
|
||||
|
||||
Start the HTTP server:
|
||||
|
||||
```bash
|
||||
npm run start:sse
|
||||
```
|
||||
|
||||
The server will be available at `http://localhost:3000` with the following endpoints:
|
||||
|
||||
- `/health` - Health check
|
||||
- `/api/*` - REST API endpoints
|
||||
- `/mcp/sse` - Server-Sent Events endpoint
|
||||
- `/mcp/rpc` - JSON-RPC endpoint
|
||||
- `/api-docs` - Interactive Swagger documentation
|
||||
|
||||
## HTTP API
|
||||
|
||||
### System Information Endpoints
|
||||
|
||||
#### Get CPU Information
|
||||
|
||||
```bash
|
||||
GET /api/cpu
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"model": "Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz",
|
||||
"cores": 6,
|
||||
"processors": 12,
|
||||
"mhz": 2600.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Memory Information
|
||||
|
||||
```bash
|
||||
GET /api/memory
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"total": 16384000,
|
||||
"free": 4096000,
|
||||
"available": 8192000,
|
||||
"buffers": 512000,
|
||||
"cached": 2048000,
|
||||
"swapTotal": 4096000,
|
||||
"swapFree": 4096000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Load Average
|
||||
|
||||
```bash
|
||||
GET /api/load
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"one": 1.5,
|
||||
"five": 1.2,
|
||||
"fifteen": 0.9,
|
||||
"runningProcesses": 2,
|
||||
"totalProcesses": 350
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Network Statistics
|
||||
|
||||
```bash
|
||||
GET /api/network?interface=eth0
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"interface": "eth0",
|
||||
"rxBytes": 1048576000,
|
||||
"rxPackets": 1000000,
|
||||
"rxErrors": 0,
|
||||
"rxDropped": 0,
|
||||
"txBytes": 524288000,
|
||||
"txPackets": 500000,
|
||||
"txErrors": 0,
|
||||
"txDropped": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### ProcFS Operations
|
||||
|
||||
#### Read ProcFS File
|
||||
|
||||
```bash
|
||||
GET /api/procfs?path=sys/kernel/hostname&format=raw
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": "myserver\n"
|
||||
}
|
||||
```
|
||||
|
||||
#### Write ProcFS File
|
||||
|
||||
```bash
|
||||
POST /api/procfs
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"path": "sys/kernel/hostname",
|
||||
"value": "newserver"
|
||||
}
|
||||
```
|
||||
|
||||
### Sysctl Operations
|
||||
|
||||
#### Read Sysctl Parameter
|
||||
|
||||
```bash
|
||||
GET /api/sysctl/net.ipv4.ip_forward
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"key": "net.ipv4.ip_forward",
|
||||
"value": 0,
|
||||
"writable": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Write Sysctl Parameter
|
||||
|
||||
```bash
|
||||
POST /api/sysctl
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"key": "net.ipv4.ip_forward",
|
||||
"value": 1
|
||||
}
|
||||
```
|
||||
|
||||
#### List All Sysctl Parameters
|
||||
|
||||
```bash
|
||||
GET /api/sysctl
|
||||
```
|
||||
|
||||
### Process Management
|
||||
|
||||
#### List All Processes
|
||||
|
||||
```bash
|
||||
GET /api/processes
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [1, 2, 3, 100, 101, ...]
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Process Information
|
||||
|
||||
```bash
|
||||
GET /api/processes/1
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"pid": 1,
|
||||
"name": "systemd",
|
||||
"state": "S",
|
||||
"ppid": 0,
|
||||
"threads": 1,
|
||||
"vmSize": 168960,
|
||||
"vmRss": 13312
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Set Process Priority
|
||||
|
||||
```bash
|
||||
POST /api/processes/12345/priority
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"priority": 10
|
||||
}
|
||||
```
|
||||
|
||||
## JSON-RPC API
|
||||
|
||||
Send JSON-RPC requests to `/mcp/rpc`:
|
||||
|
||||
```bash
|
||||
POST /mcp/rpc
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "tools/list",
|
||||
"params": {}
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"tools": [
|
||||
{
|
||||
"name": "get_cpu_info",
|
||||
"description": "Get CPU information"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Call a Tool
|
||||
|
||||
```bash
|
||||
POST /mcp/rpc
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "get_cpu_info",
|
||||
"arguments": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## SSE Events
|
||||
|
||||
Connect to the SSE endpoint to receive real-time updates:
|
||||
|
||||
```javascript
|
||||
const eventSource = new EventSource('http://localhost:3000/mcp/sse');
|
||||
|
||||
eventSource.addEventListener('connected', (event) => {
|
||||
console.log('Connected:', JSON.parse(event.data));
|
||||
});
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
console.error('Error:', error);
|
||||
};
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### cURL Examples
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl http://localhost:3000/health
|
||||
|
||||
# Get CPU info
|
||||
curl http://localhost:3000/api/cpu
|
||||
|
||||
# Get memory info
|
||||
curl http://localhost:3000/api/memory
|
||||
|
||||
# Read sysctl
|
||||
curl http://localhost:3000/api/sysctl/kernel.hostname
|
||||
|
||||
# Write sysctl (requires permissions)
|
||||
curl -X POST http://localhost:3000/api/sysctl \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key":"net.ipv4.ip_forward","value":1}'
|
||||
|
||||
# Get process info
|
||||
curl http://localhost:3000/api/processes/1
|
||||
```
|
||||
|
||||
### JavaScript/Node.js Example
|
||||
|
||||
```javascript
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
async function getCPUInfo() {
|
||||
const response = await fetch('http://localhost:3000/api/cpu');
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
getCPUInfo();
|
||||
```
|
||||
|
||||
### Python Example
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.get('http://localhost:3000/api/cpu')
|
||||
data = response.json()
|
||||
print(data)
|
||||
```
|
||||
|
||||
### Using with MCP Client
|
||||
|
||||
Configure your MCP client (e.g., Claude Desktop):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"procfs": {
|
||||
"command": "node",
|
||||
"args": ["/path/to/mcp-proc/dist/cli.js"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then use the tools in your MCP client:
|
||||
|
||||
```
|
||||
Get CPU information using the procfs server
|
||||
```
|
||||
|
||||
The client will automatically call the appropriate tool and format the response.
|
||||
|
||||
## Error Handling
|
||||
|
||||
All endpoints return errors in a consistent format:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Error message here"
|
||||
}
|
||||
```
|
||||
|
||||
HTTP status codes:
|
||||
- `200` - Success
|
||||
- `400` - Bad Request (invalid parameters)
|
||||
- `403` - Forbidden (insufficient permissions)
|
||||
- `404` - Not Found
|
||||
- `500` - Internal Server Error
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
Currently no rate limiting is implemented. For production use, consider adding rate limiting middleware.
|
||||
|
||||
## Authentication
|
||||
|
||||
Currently no authentication is required. For production use, implement authentication using:
|
||||
|
||||
- API keys
|
||||
- JWT tokens
|
||||
- OAuth 2.0
|
||||
- Basic Auth
|
||||
|
||||
Example with API key middleware:
|
||||
|
||||
```typescript
|
||||
app.use((req, res, next) => {
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
if (apiKey !== process.env.API_KEY) {
|
||||
return res.status(403).json({ error: 'Invalid API key' });
|
||||
}
|
||||
next();
|
||||
});
|
||||
```
|
||||
512
docs/DEPLOYMENT.md
Archivo normal
512
docs/DEPLOYMENT.md
Archivo normal
@@ -0,0 +1,512 @@
|
||||
# Deployment Guide
|
||||
|
||||
This guide covers deploying MCP ProcFS Server in production environments.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Linux server (Ubuntu 20.04+, Debian 11+, or similar)
|
||||
- Node.js 18 or higher
|
||||
- sudo/root access for system operations
|
||||
- systemd (for service management)
|
||||
|
||||
## Installation on Server
|
||||
|
||||
### Option 1: From npm (recommended)
|
||||
|
||||
```bash
|
||||
# Install globally
|
||||
sudo npm install -g @mcp/procfs-server
|
||||
|
||||
# Verify installation
|
||||
mcp-procfs --version
|
||||
```
|
||||
|
||||
### Option 2: From source
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/cameronrye/activitypub-mcp.git
|
||||
cd activitypub-mcp/mcp-proc
|
||||
|
||||
# Install and build
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
# Link globally (optional)
|
||||
sudo npm link
|
||||
```
|
||||
|
||||
## Running as a Service
|
||||
|
||||
### systemd Service File
|
||||
|
||||
Create `/etc/systemd/system/mcp-procfs.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=MCP ProcFS Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=mcp-procfs
|
||||
Group=mcp-procfs
|
||||
WorkingDirectory=/opt/mcp-procfs
|
||||
Environment="NODE_ENV=production"
|
||||
Environment="PORT=3000"
|
||||
ExecStart=/usr/bin/node /usr/local/lib/node_modules/@mcp/procfs-server/dist/cli-sse.js
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=mcp-procfs
|
||||
|
||||
# Security settings
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadWritePaths=/var/log/mcp-procfs
|
||||
|
||||
# Required capabilities
|
||||
CapabilityBoundingSet=CAP_SYS_NICE CAP_SYS_ADMIN CAP_DAC_OVERRIDE
|
||||
AmbientCapabilities=CAP_SYS_NICE CAP_SYS_ADMIN CAP_DAC_OVERRIDE
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
### Setup Service
|
||||
|
||||
```bash
|
||||
# Create user
|
||||
sudo useradd -r -s /bin/false mcp-procfs
|
||||
|
||||
# Create working directory
|
||||
sudo mkdir -p /opt/mcp-procfs
|
||||
sudo chown mcp-procfs:mcp-procfs /opt/mcp-procfs
|
||||
|
||||
# Create log directory
|
||||
sudo mkdir -p /var/log/mcp-procfs
|
||||
sudo chown mcp-procfs:mcp-procfs /var/log/mcp-procfs
|
||||
|
||||
# Reload systemd
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
# Enable and start service
|
||||
sudo systemctl enable mcp-procfs
|
||||
sudo systemctl start mcp-procfs
|
||||
|
||||
# Check status
|
||||
sudo systemctl status mcp-procfs
|
||||
|
||||
# View logs
|
||||
sudo journalctl -u mcp-procfs -f
|
||||
```
|
||||
|
||||
## Nginx Reverse Proxy
|
||||
|
||||
### Install Nginx
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install nginx
|
||||
```
|
||||
|
||||
### Configure Nginx
|
||||
|
||||
Create `/etc/nginx/sites-available/mcp-procfs`:
|
||||
|
||||
```nginx
|
||||
upstream mcp_procfs {
|
||||
server 127.0.0.1:3000;
|
||||
keepalive 64;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name procfs.example.com;
|
||||
|
||||
# Redirect to HTTPS
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name procfs.example.com;
|
||||
|
||||
# SSL certificates (use Let's Encrypt)
|
||||
ssl_certificate /etc/letsencrypt/live/procfs.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/procfs.example.com/privkey.pem;
|
||||
|
||||
# SSL configuration
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
|
||||
# Rate limiting
|
||||
limit_req_zone $binary_remote_addr zone=mcp_limit:10m rate=10r/s;
|
||||
limit_req zone=mcp_limit burst=20 nodelay;
|
||||
|
||||
# Proxy settings
|
||||
location / {
|
||||
proxy_pass http://mcp_procfs;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# SSE configuration
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
|
||||
# API documentation
|
||||
location /api-docs {
|
||||
proxy_pass http://mcp_procfs/api-docs;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
# Health check
|
||||
location /health {
|
||||
proxy_pass http://mcp_procfs/health;
|
||||
access_log off;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Enable Site
|
||||
|
||||
```bash
|
||||
# Create symlink
|
||||
sudo ln -s /etc/nginx/sites-available/mcp-procfs /etc/nginx/sites-enabled/
|
||||
|
||||
# Test configuration
|
||||
sudo nginx -t
|
||||
|
||||
# Reload Nginx
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
## SSL/TLS with Let's Encrypt
|
||||
|
||||
```bash
|
||||
# Install certbot
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
|
||||
# Obtain certificate
|
||||
sudo certbot --nginx -d procfs.example.com
|
||||
|
||||
# Auto-renewal is set up automatically
|
||||
# Test renewal
|
||||
sudo certbot renew --dry-run
|
||||
```
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
Create `/opt/mcp-procfs/.env`:
|
||||
|
||||
```bash
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
LOG_LEVEL=info
|
||||
```
|
||||
|
||||
Update systemd service to use env file:
|
||||
|
||||
```ini
|
||||
[Service]
|
||||
EnvironmentFile=/opt/mcp-procfs/.env
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Prometheus Metrics (future enhancement)
|
||||
|
||||
The server can be extended to expose metrics:
|
||||
|
||||
```typescript
|
||||
// Add to server-sse.ts
|
||||
import promClient from 'prom-client';
|
||||
|
||||
const register = new promClient.Registry();
|
||||
const httpRequestDuration = new promClient.Histogram({
|
||||
name: 'http_request_duration_seconds',
|
||||
help: 'Duration of HTTP requests in seconds',
|
||||
labelNames: ['method', 'route', 'status'],
|
||||
});
|
||||
register.registerMetric(httpRequestDuration);
|
||||
|
||||
app.get('/metrics', async (req, res) => {
|
||||
res.set('Content-Type', register.contentType);
|
||||
res.end(await register.metrics());
|
||||
});
|
||||
```
|
||||
|
||||
### Log Rotation
|
||||
|
||||
Create `/etc/logrotate.d/mcp-procfs`:
|
||||
|
||||
```
|
||||
/var/log/mcp-procfs/*.log {
|
||||
daily
|
||||
missingok
|
||||
rotate 14
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
create 0640 mcp-procfs mcp-procfs
|
||||
sharedscripts
|
||||
postrotate
|
||||
systemctl reload mcp-procfs > /dev/null 2>&1 || true
|
||||
endscript
|
||||
}
|
||||
```
|
||||
|
||||
## Security Hardening
|
||||
|
||||
### Firewall (UFW)
|
||||
|
||||
```bash
|
||||
# Allow SSH
|
||||
sudo ufw allow ssh
|
||||
|
||||
# Allow HTTP and HTTPS
|
||||
sudo ufw allow 80/tcp
|
||||
sudo ufw allow 443/tcp
|
||||
|
||||
# Enable firewall
|
||||
sudo ufw enable
|
||||
|
||||
# Check status
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
### Fail2ban (optional)
|
||||
|
||||
Create `/etc/fail2ban/filter.d/mcp-procfs.conf`:
|
||||
|
||||
```ini
|
||||
[Definition]
|
||||
failregex = ^<HOST> .* "POST /api/.*" 401
|
||||
^<HOST> .* "POST /api/.*" 403
|
||||
ignoreregex =
|
||||
```
|
||||
|
||||
Create `/etc/fail2ban/jail.d/mcp-procfs.conf`:
|
||||
|
||||
```ini
|
||||
[mcp-procfs]
|
||||
enabled = true
|
||||
port = http,https
|
||||
filter = mcp-procfs
|
||||
logpath = /var/log/nginx/access.log
|
||||
maxretry = 5
|
||||
bantime = 3600
|
||||
```
|
||||
|
||||
## Backup
|
||||
|
||||
### Configuration Backup
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# /opt/mcp-procfs/backup.sh
|
||||
|
||||
BACKUP_DIR="/var/backups/mcp-procfs"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# Backup configuration
|
||||
tar -czf $BACKUP_DIR/config_$DATE.tar.gz \
|
||||
/etc/systemd/system/mcp-procfs.service \
|
||||
/etc/nginx/sites-available/mcp-procfs \
|
||||
/opt/mcp-procfs/.env
|
||||
|
||||
# Keep only last 7 days
|
||||
find $BACKUP_DIR -name "config_*.tar.gz" -mtime +7 -delete
|
||||
```
|
||||
|
||||
Add to crontab:
|
||||
|
||||
```bash
|
||||
0 2 * * * /opt/mcp-procfs/backup.sh
|
||||
```
|
||||
|
||||
## Health Checks
|
||||
|
||||
### External Monitoring
|
||||
|
||||
Use services like UptimeRobot, Pingdom, or custom scripts:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# health-check.sh
|
||||
|
||||
ENDPOINT="https://procfs.example.com/health"
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" $ENDPOINT)
|
||||
|
||||
if [ $RESPONSE -eq 200 ]; then
|
||||
echo "OK: Server is healthy"
|
||||
exit 0
|
||||
else
|
||||
echo "ERROR: Server returned $RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
### Node.js Options
|
||||
|
||||
Update systemd service:
|
||||
|
||||
```ini
|
||||
[Service]
|
||||
Environment="NODE_OPTIONS=--max-old-space-size=2048"
|
||||
```
|
||||
|
||||
### Nginx Tuning
|
||||
|
||||
Add to nginx.conf:
|
||||
|
||||
```nginx
|
||||
worker_processes auto;
|
||||
worker_rlimit_nofile 65535;
|
||||
|
||||
events {
|
||||
worker_connections 4096;
|
||||
use epoll;
|
||||
}
|
||||
```
|
||||
|
||||
## Scaling
|
||||
|
||||
### Horizontal Scaling with PM2
|
||||
|
||||
```bash
|
||||
# Install PM2
|
||||
npm install -g pm2
|
||||
|
||||
# Start with cluster mode
|
||||
pm2 start dist/cli-sse.js -i max --name mcp-procfs
|
||||
|
||||
# Save configuration
|
||||
pm2 save
|
||||
|
||||
# Setup startup script
|
||||
pm2 startup
|
||||
```
|
||||
|
||||
### Load Balancing
|
||||
|
||||
Update Nginx upstream:
|
||||
|
||||
```nginx
|
||||
upstream mcp_procfs {
|
||||
least_conn;
|
||||
server 127.0.0.1:3000;
|
||||
server 127.0.0.1:3001;
|
||||
server 127.0.0.1:3002;
|
||||
server 127.0.0.1:3003;
|
||||
keepalive 64;
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Service won't start
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
sudo journalctl -u mcp-procfs -n 50
|
||||
|
||||
# Check permissions
|
||||
sudo -u mcp-procfs /usr/bin/node --version
|
||||
|
||||
# Verify installation
|
||||
which node
|
||||
node --version
|
||||
```
|
||||
|
||||
### Permission errors
|
||||
|
||||
```bash
|
||||
# Grant capabilities
|
||||
sudo setcap cap_sys_nice,cap_sys_admin+ep /usr/bin/node
|
||||
|
||||
# Or run as root (not recommended)
|
||||
sudo systemctl edit mcp-procfs
|
||||
# Add: User=root
|
||||
```
|
||||
|
||||
### High memory usage
|
||||
|
||||
```bash
|
||||
# Monitor with htop
|
||||
htop
|
||||
|
||||
# Check Node.js heap
|
||||
node --expose-gc --max-old-space-size=512 dist/cli-sse.js
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Updates
|
||||
|
||||
```bash
|
||||
# Stop service
|
||||
sudo systemctl stop mcp-procfs
|
||||
|
||||
# Update package
|
||||
sudo npm update -g @mcp/procfs-server
|
||||
|
||||
# Start service
|
||||
sudo systemctl start mcp-procfs
|
||||
|
||||
# Verify
|
||||
curl http://localhost:3000/health
|
||||
```
|
||||
|
||||
### Rolling Restart
|
||||
|
||||
```bash
|
||||
# With PM2
|
||||
pm2 reload mcp-procfs
|
||||
|
||||
# With systemd
|
||||
sudo systemctl restart mcp-procfs
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Server provisioned
|
||||
- [ ] Node.js installed
|
||||
- [ ] MCP ProcFS Server installed
|
||||
- [ ] systemd service configured
|
||||
- [ ] Service running and enabled
|
||||
- [ ] Nginx installed and configured
|
||||
- [ ] SSL certificates obtained
|
||||
- [ ] Firewall configured
|
||||
- [ ] Monitoring set up
|
||||
- [ ] Backups configured
|
||||
- [ ] Documentation updated
|
||||
- [ ] Team trained
|
||||
|
||||
## Support
|
||||
|
||||
For production support:
|
||||
- GitHub Issues: https://github.com/cameronrye/activitypub-mcp/issues
|
||||
- Documentation: https://github.com/cameronrye/activitypub-mcp/tree/master/mcp-proc
|
||||
319
docs/DEVELOPMENT.md
Archivo normal
319
docs/DEVELOPMENT.md
Archivo normal
@@ -0,0 +1,319 @@
|
||||
# Development Guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 18 or higher
|
||||
- Linux operating system (for full functionality)
|
||||
- Basic understanding of procfs and Linux system internals
|
||||
- TypeScript knowledge
|
||||
|
||||
## Setup Development Environment
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/cameronrye/activitypub-mcp.git
|
||||
cd activitypub-mcp/mcp-proc
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build the project
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
mcp-proc/
|
||||
├── src/
|
||||
│ ├── lib/ # Core library code
|
||||
│ │ ├── procfs-reader.ts # ProcFS reading logic
|
||||
│ │ └── procfs-writer.ts # ProcFS writing logic
|
||||
│ ├── types/ # Type definitions
|
||||
│ │ ├── procfs.ts # ProcFS types
|
||||
│ │ ├── mcp.ts # MCP protocol types
|
||||
│ │ └── schemas.ts # Zod validation schemas
|
||||
│ ├── server.ts # MCP JSON-RPC server
|
||||
│ ├── server-sse.ts # HTTP/SSE server
|
||||
│ ├── cli.ts # CLI for JSON-RPC server
|
||||
│ ├── cli-sse.ts # CLI for HTTP server
|
||||
│ └── index.ts # Main exports
|
||||
├── examples/ # Usage examples
|
||||
├── scripts/ # Build and setup scripts
|
||||
├── tests/ # Test files
|
||||
└── docs/ # Documentation
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Running in Development Mode
|
||||
|
||||
```bash
|
||||
# Watch mode with hot reload
|
||||
npm run dev
|
||||
|
||||
# For HTTP/SSE server
|
||||
npm run start:sse
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
# Compile TypeScript
|
||||
npm run build
|
||||
|
||||
# Clean build
|
||||
rm -rf dist/ && npm run build
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
npm test
|
||||
|
||||
# Watch mode
|
||||
npm run test:watch
|
||||
|
||||
# Coverage report
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
### Linting and Formatting
|
||||
|
||||
```bash
|
||||
# Check code style
|
||||
npm run lint
|
||||
|
||||
# Format code
|
||||
npm run format
|
||||
```
|
||||
|
||||
## Adding New Features
|
||||
|
||||
### Adding a New ProcFS Reader
|
||||
|
||||
1. Add the method to `src/lib/procfs-reader.ts`:
|
||||
|
||||
```typescript
|
||||
async getNewMetric(): Promise<NewMetricType> {
|
||||
const content = await this.readRaw('path/to/file');
|
||||
// Parse content
|
||||
return parsedData;
|
||||
}
|
||||
```
|
||||
|
||||
2. Add the type definition in `src/types/procfs.ts`:
|
||||
|
||||
```typescript
|
||||
export interface NewMetricType {
|
||||
field1: string;
|
||||
field2: number;
|
||||
}
|
||||
```
|
||||
|
||||
3. Add validation schema in `src/types/schemas.ts`:
|
||||
|
||||
```typescript
|
||||
export const NewMetricRequestSchema = z.object({
|
||||
param: z.string(),
|
||||
});
|
||||
```
|
||||
|
||||
4. Add tool definition in `src/server.ts`:
|
||||
|
||||
```typescript
|
||||
{
|
||||
name: 'get_new_metric',
|
||||
description: 'Get new metric',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
param: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. Add HTTP endpoint in `src/server-sse.ts`:
|
||||
|
||||
```typescript
|
||||
this.app.get('/api/new-metric', async (req, res) => {
|
||||
try {
|
||||
const data = await this.reader.getNewMetric();
|
||||
res.json({ success: true, data });
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: (error as Error).message
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Adding Tests
|
||||
|
||||
Create test file in `tests/`:
|
||||
|
||||
```typescript
|
||||
import { ProcFSReader } from '../src/lib/procfs-reader';
|
||||
|
||||
describe('ProcFSReader', () => {
|
||||
let reader: ProcFSReader;
|
||||
|
||||
beforeEach(() => {
|
||||
reader = new ProcFSReader();
|
||||
});
|
||||
|
||||
test('should read CPU info', async () => {
|
||||
const info = await reader.getCPUInfo();
|
||||
expect(info).toHaveProperty('model');
|
||||
expect(info).toHaveProperty('cores');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
- Use TypeScript strict mode
|
||||
- Follow ESLint rules
|
||||
- Use Prettier for formatting
|
||||
- Add JSDoc comments for public APIs
|
||||
- Use async/await for asynchronous code
|
||||
- Handle errors gracefully
|
||||
- Validate inputs with Zod schemas
|
||||
|
||||
## Debugging
|
||||
|
||||
### Debug MCP Server
|
||||
|
||||
```bash
|
||||
# With debug output
|
||||
DEBUG=* npm start
|
||||
```
|
||||
|
||||
### Debug HTTP Server
|
||||
|
||||
```bash
|
||||
# Check server logs
|
||||
npm run start:sse
|
||||
|
||||
# Test endpoints
|
||||
curl http://localhost:3000/health
|
||||
```
|
||||
|
||||
### Using VS Code Debugger
|
||||
|
||||
Create `.vscode/launch.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Server",
|
||||
"program": "${workspaceFolder}/src/cli.ts",
|
||||
"preLaunchTask": "npm: build",
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Permission Errors
|
||||
|
||||
Some operations require elevated permissions:
|
||||
|
||||
```bash
|
||||
# Run with sudo (not recommended for development)
|
||||
sudo npm start
|
||||
|
||||
# Better: Use capabilities
|
||||
sudo setcap cap_sys_nice,cap_sys_admin+ep $(which node)
|
||||
```
|
||||
|
||||
### TypeScript Errors
|
||||
|
||||
```bash
|
||||
# Clean and rebuild
|
||||
rm -rf dist/ node_modules/
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
```bash
|
||||
# Change port
|
||||
PORT=8080 npm run start:sse
|
||||
|
||||
# Kill process using port 3000
|
||||
lsof -ti:3000 | xargs kill -9
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Add tests
|
||||
5. Run linting and tests
|
||||
6. Submit a pull request
|
||||
|
||||
### Commit Message Format
|
||||
|
||||
```
|
||||
type(scope): subject
|
||||
|
||||
body
|
||||
|
||||
footer
|
||||
```
|
||||
|
||||
Types:
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation
|
||||
- `style`: Formatting
|
||||
- `refactor`: Code restructuring
|
||||
- `test`: Adding tests
|
||||
- `chore`: Maintenance
|
||||
|
||||
Example:
|
||||
```
|
||||
feat(procfs): add disk temperature reading
|
||||
|
||||
Add support for reading disk temperature from /sys/class/hwmon.
|
||||
Includes new getDiskTemperature() method in ProcFSReader.
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
## Release Process
|
||||
|
||||
1. Update version in `package.json`
|
||||
2. Update `CHANGELOG.md`
|
||||
3. Run tests: `npm test`
|
||||
4. Build: `npm run build`
|
||||
5. Commit changes
|
||||
6. Create git tag: `git tag v1.0.0`
|
||||
7. Push: `git push --follow-tags`
|
||||
8. Publish: `npm publish`
|
||||
|
||||
Or use the release script:
|
||||
|
||||
```bash
|
||||
./scripts/release.sh 1.0.0
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [MCP Specification](https://modelcontextprotocol.io)
|
||||
- [TypeScript Documentation](https://www.typescriptlang.org/docs/)
|
||||
- [Express.js Guide](https://expressjs.com/en/guide/routing.html)
|
||||
- [Zod Documentation](https://zod.dev/)
|
||||
- [Jest Testing Framework](https://jestjs.io/)
|
||||
204
docs/QUICKSTART.md
Archivo normal
204
docs/QUICKSTART.md
Archivo normal
@@ -0,0 +1,204 @@
|
||||
# Quick Start Guide
|
||||
|
||||
## Installation
|
||||
|
||||
### Option 1: From npm (when published)
|
||||
|
||||
```bash
|
||||
npm install -g @mcp/procfs-server
|
||||
```
|
||||
|
||||
### Option 2: From source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/cameronrye/activitypub-mcp.git
|
||||
cd activitypub-mcp/mcp-proc
|
||||
./scripts/setup.sh
|
||||
```
|
||||
|
||||
## First Run
|
||||
|
||||
### JSON-RPC Server (stdio)
|
||||
|
||||
```bash
|
||||
# Using global install
|
||||
mcp-procfs
|
||||
|
||||
# Or from source
|
||||
npm start
|
||||
```
|
||||
|
||||
### HTTP Server with SSE
|
||||
|
||||
```bash
|
||||
# Default port 3000
|
||||
npm run start:sse
|
||||
|
||||
# Custom port
|
||||
PORT=8080 npm run start:sse
|
||||
```
|
||||
|
||||
Open browser to http://localhost:3000/api-docs to explore the API.
|
||||
|
||||
## Basic Usage Examples
|
||||
|
||||
### Get System Information
|
||||
|
||||
```bash
|
||||
# CPU information
|
||||
curl http://localhost:3000/api/cpu
|
||||
|
||||
# Memory information
|
||||
curl http://localhost:3000/api/memory
|
||||
|
||||
# Load average
|
||||
curl http://localhost:3000/api/load
|
||||
|
||||
# Network statistics
|
||||
curl http://localhost:3000/api/network
|
||||
```
|
||||
|
||||
### Process Management
|
||||
|
||||
```bash
|
||||
# List all processes
|
||||
curl http://localhost:3000/api/processes
|
||||
|
||||
# Get process info
|
||||
curl http://localhost:3000/api/processes/1
|
||||
|
||||
# Set process priority (requires permissions)
|
||||
curl -X POST http://localhost:3000/api/processes/1234/priority \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"priority": 10}'
|
||||
```
|
||||
|
||||
### Sysctl Operations
|
||||
|
||||
```bash
|
||||
# Read parameter
|
||||
curl http://localhost:3000/api/sysctl/kernel.hostname
|
||||
|
||||
# Write parameter (requires permissions)
|
||||
curl -X POST http://localhost:3000/api/sysctl \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key": "net.ipv4.ip_forward", "value": 1}'
|
||||
```
|
||||
|
||||
## Using with MCP Client
|
||||
|
||||
### Claude Desktop Configuration
|
||||
|
||||
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"procfs": {
|
||||
"command": "mcp-procfs"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then restart Claude Desktop and use natural language:
|
||||
|
||||
```
|
||||
"What's the current CPU usage?"
|
||||
"Show me memory statistics"
|
||||
"List all running processes"
|
||||
"What's the system load average?"
|
||||
```
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Monitor System Resources
|
||||
|
||||
```bash
|
||||
# Real-time monitoring
|
||||
node examples/monitoring-dashboard.js
|
||||
```
|
||||
|
||||
### Read Custom ProcFS Files
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3000/api/procfs?path=sys/kernel/hostname&format=raw"
|
||||
```
|
||||
|
||||
### Adjust System Parameters
|
||||
|
||||
```bash
|
||||
# Check current value
|
||||
curl http://localhost:3000/api/sysctl/vm.swappiness
|
||||
|
||||
# Change value (requires root)
|
||||
sudo curl -X POST http://localhost:3000/api/sysctl \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key": "vm.swappiness", "value": 10}'
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Permission Denied
|
||||
|
||||
Some operations require elevated permissions:
|
||||
|
||||
```bash
|
||||
# Option 1: Run with sudo (not recommended)
|
||||
sudo npm run start:sse
|
||||
|
||||
# Option 2: Use capabilities
|
||||
sudo setcap cap_sys_nice,cap_sys_admin+ep $(which node)
|
||||
npm run start:sse
|
||||
```
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
```bash
|
||||
# Use different port
|
||||
PORT=8080 npm run start:sse
|
||||
|
||||
# Or kill existing process
|
||||
lsof -ti:3000 | xargs kill -9
|
||||
```
|
||||
|
||||
### Cannot Read /proc Files
|
||||
|
||||
Ensure you're running on Linux:
|
||||
|
||||
```bash
|
||||
uname -s # Should output: Linux
|
||||
```
|
||||
|
||||
Check file permissions:
|
||||
|
||||
```bash
|
||||
ls -la /proc/cpuinfo
|
||||
cat /proc/cpuinfo
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Read the full [API Documentation](docs/API.md)
|
||||
- Check out [Examples](examples/)
|
||||
- Learn about [Development](docs/DEVELOPMENT.md)
|
||||
- Review [Security Considerations](README.md#security-considerations)
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Issues: https://github.com/cameronrye/activitypub-mcp/issues
|
||||
- Discussions: https://github.com/cameronrye/activitypub-mcp/discussions
|
||||
- Documentation: https://github.com/cameronrye/activitypub-mcp/tree/master/mcp-proc
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| Start JSON-RPC server | `mcp-procfs` or `npm start` |
|
||||
| Start HTTP server | `npm run start:sse` |
|
||||
| View API docs | http://localhost:3000/api-docs |
|
||||
| Get CPU info | `curl localhost:3000/api/cpu` |
|
||||
| Get memory info | `curl localhost:3000/api/memory` |
|
||||
| List processes | `curl localhost:3000/api/processes` |
|
||||
| Read sysctl | `curl localhost:3000/api/sysctl/KEY` |
|
||||
| Health check | `curl localhost:3000/health` |
|
||||
134
examples/README.md
Archivo normal
134
examples/README.md
Archivo normal
@@ -0,0 +1,134 @@
|
||||
# Examples
|
||||
|
||||
This directory contains example code demonstrating how to use the MCP ProcFS Server.
|
||||
|
||||
## Files
|
||||
|
||||
### `basic-usage.ts`
|
||||
|
||||
TypeScript example showing how to use the ProcFS reader and writer libraries directly.
|
||||
|
||||
**Run:**
|
||||
```bash
|
||||
cd /path/to/mcp-proc
|
||||
npm install
|
||||
npm run build
|
||||
npx tsx examples/basic-usage.ts
|
||||
```
|
||||
|
||||
### `python-client.py`
|
||||
|
||||
Python example demonstrating how to interact with the HTTP API.
|
||||
|
||||
**Requirements:**
|
||||
```bash
|
||||
pip install requests
|
||||
```
|
||||
|
||||
**Run:**
|
||||
```bash
|
||||
# Start the server first
|
||||
npm run start:sse
|
||||
|
||||
# In another terminal
|
||||
python examples/python-client.py
|
||||
```
|
||||
|
||||
### `monitoring-dashboard.js`
|
||||
|
||||
Real-time system monitoring dashboard using Server-Sent Events (SSE).
|
||||
|
||||
**Requirements:**
|
||||
```bash
|
||||
npm install eventsource node-fetch
|
||||
```
|
||||
|
||||
**Run:**
|
||||
```bash
|
||||
# Start the server first
|
||||
npm run start:sse
|
||||
|
||||
# In another terminal
|
||||
node examples/monitoring-dashboard.js
|
||||
```
|
||||
|
||||
### `mcp-config.json`
|
||||
|
||||
Example MCP client configuration file. Use this with MCP-compatible clients like Claude Desktop.
|
||||
|
||||
**Usage:**
|
||||
|
||||
1. Copy to your MCP client config location
|
||||
2. Update the path to point to your installation
|
||||
3. Restart your MCP client
|
||||
|
||||
For Claude Desktop on macOS:
|
||||
```bash
|
||||
cp examples/mcp-config.json ~/Library/Application\ Support/Claude/claude_desktop_config.json
|
||||
```
|
||||
|
||||
## More Examples
|
||||
|
||||
### Using cURL
|
||||
|
||||
```bash
|
||||
# Start the HTTP server
|
||||
npm run start:sse
|
||||
|
||||
# Get CPU information
|
||||
curl http://localhost:3000/api/cpu
|
||||
|
||||
# Get memory info
|
||||
curl http://localhost:3000/api/memory
|
||||
|
||||
# Read a sysctl parameter
|
||||
curl http://localhost:3000/api/sysctl/kernel.hostname
|
||||
|
||||
# Write a sysctl parameter (requires permissions)
|
||||
curl -X POST http://localhost:3000/api/sysctl \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key": "vm.swappiness", "value": 10}'
|
||||
```
|
||||
|
||||
### Using JavaScript Fetch API
|
||||
|
||||
```javascript
|
||||
// Get CPU information
|
||||
const response = await fetch('http://localhost:3000/api/cpu');
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
|
||||
// Write sysctl parameter
|
||||
await fetch('http://localhost:3000/api/sysctl', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
key: 'net.ipv4.ip_forward',
|
||||
value: 1
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### Using the MCP Protocol
|
||||
|
||||
```bash
|
||||
# Start the MCP server on stdio
|
||||
npm start
|
||||
|
||||
# Send a JSON-RPC request (via stdin)
|
||||
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | npm start
|
||||
```
|
||||
|
||||
## Interactive API Documentation
|
||||
|
||||
The HTTP server includes interactive Swagger documentation:
|
||||
|
||||
1. Start the server: `npm run start:sse`
|
||||
2. Open browser: http://localhost:3000/api-docs
|
||||
3. Try out the endpoints directly from the browser
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Check the [API Documentation](../docs/API.md)
|
||||
- See [Quick Start Guide](../docs/QUICKSTART.md)
|
||||
- Review the [main README](../README.md)
|
||||
89
examples/basic-usage.ts
Archivo normal
89
examples/basic-usage.ts
Archivo normal
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Example: Using MCP ProcFS Server as a library
|
||||
*/
|
||||
|
||||
import { ProcFSReader, ProcFSWriter } from '../src/index';
|
||||
|
||||
async function main() {
|
||||
const reader = new ProcFSReader();
|
||||
const writer = new ProcFSWriter();
|
||||
|
||||
console.log('=== System Information ===\n');
|
||||
|
||||
// Get CPU information
|
||||
const cpuInfo = await reader.getCPUInfo();
|
||||
console.log('CPU Information:');
|
||||
console.log(` Model: ${cpuInfo.model}`);
|
||||
console.log(` Cores: ${cpuInfo.cores}`);
|
||||
console.log(` Processors: ${cpuInfo.processors}`);
|
||||
console.log(` MHz: ${cpuInfo.mhz}`);
|
||||
console.log();
|
||||
|
||||
// Get memory information
|
||||
const memInfo = await reader.getMemInfo();
|
||||
console.log('Memory Information:');
|
||||
console.log(` Total: ${(memInfo.total / 1024).toFixed(2)} GB`);
|
||||
console.log(` Free: ${(memInfo.free / 1024).toFixed(2)} GB`);
|
||||
console.log(` Available: ${(memInfo.available / 1024).toFixed(2)} GB`);
|
||||
console.log(` Cached: ${(memInfo.cached / 1024).toFixed(2)} GB`);
|
||||
console.log();
|
||||
|
||||
// Get load average
|
||||
const loadAvg = await reader.getLoadAvg();
|
||||
console.log('Load Average:');
|
||||
console.log(` 1 min: ${loadAvg.one}`);
|
||||
console.log(` 5 min: ${loadAvg.five}`);
|
||||
console.log(` 15 min: ${loadAvg.fifteen}`);
|
||||
console.log(` Running/Total: ${loadAvg.runningProcesses}/${loadAvg.totalProcesses}`);
|
||||
console.log();
|
||||
|
||||
// Get network statistics
|
||||
const netStats = await reader.getNetDevStats();
|
||||
console.log('Network Interfaces:');
|
||||
for (const iface of netStats) {
|
||||
console.log(` ${iface.interface}:`);
|
||||
console.log(` RX: ${(iface.rxBytes / 1024 / 1024).toFixed(2)} MB`);
|
||||
console.log(` TX: ${(iface.txBytes / 1024 / 1024).toFixed(2)} MB`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Get disk statistics
|
||||
const diskStats = await reader.getDiskStats();
|
||||
console.log('Disk Devices:');
|
||||
for (const disk of diskStats.slice(0, 5)) {
|
||||
if (disk.readsCompleted > 0 || disk.writesCompleted > 0) {
|
||||
console.log(` ${disk.device}:`);
|
||||
console.log(` Reads: ${disk.readsCompleted}`);
|
||||
console.log(` Writes: ${disk.writesCompleted}`);
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
|
||||
// List some processes
|
||||
const pids = await reader.listPIDs();
|
||||
console.log(`Total Processes: ${pids.length}`);
|
||||
console.log('\nFirst 5 processes:');
|
||||
for (const pid of pids.slice(0, 5)) {
|
||||
try {
|
||||
const procInfo = await reader.getProcessInfo(pid);
|
||||
console.log(` PID ${procInfo.pid}: ${procInfo.name} (${procInfo.state})`);
|
||||
} catch (error) {
|
||||
// Process may have terminated
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Read a sysctl parameter (example)
|
||||
try {
|
||||
const param = await writer.readSysctl('kernel.hostname');
|
||||
console.log('Sysctl Parameter:');
|
||||
console.log(` kernel.hostname = ${param.value}`);
|
||||
console.log();
|
||||
} catch (error) {
|
||||
console.log('Could not read sysctl parameter');
|
||||
}
|
||||
|
||||
console.log('=== Example Complete ===');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
9
examples/mcp-config.json
Archivo normal
9
examples/mcp-config.json
Archivo normal
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"procfs": {
|
||||
"command": "node",
|
||||
"args": ["/path/to/mcp-proc/dist/cli.js"],
|
||||
"description": "Linux procfs system information and management"
|
||||
}
|
||||
}
|
||||
}
|
||||
103
examples/monitoring-dashboard.js
Archivo normal
103
examples/monitoring-dashboard.js
Archivo normal
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Example: Monitoring system metrics in real-time using SSE
|
||||
*/
|
||||
|
||||
const EventSource = require('eventsource');
|
||||
|
||||
const SSE_URL = 'http://localhost:3000/mcp/sse';
|
||||
const API_URL = 'http://localhost:3000';
|
||||
|
||||
// Connect to SSE endpoint
|
||||
const eventSource = new EventSource(SSE_URL);
|
||||
|
||||
eventSource.onopen = () => {
|
||||
console.log('Connected to MCP ProcFS Server\n');
|
||||
startMonitoring();
|
||||
};
|
||||
|
||||
eventSource.addEventListener('connected', (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('Connection confirmed:', data.clientId);
|
||||
});
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
console.error('SSE Error:', error);
|
||||
};
|
||||
|
||||
async function fetchMetric(endpoint) {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}${endpoint}`);
|
||||
const data = await response.json();
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching ${endpoint}:`, error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function displayMetrics() {
|
||||
console.clear();
|
||||
console.log('=== System Monitoring Dashboard ===\n');
|
||||
|
||||
// CPU Info
|
||||
const cpu = await fetchMetric('/api/cpu');
|
||||
if (cpu) {
|
||||
console.log('CPU:');
|
||||
console.log(` Model: ${cpu.model}`);
|
||||
console.log(` Cores: ${cpu.cores}`);
|
||||
console.log(` Speed: ${cpu.mhz.toFixed(2)} MHz`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Memory Info
|
||||
const mem = await fetchMetric('/api/memory');
|
||||
if (mem) {
|
||||
const usedPercent = ((mem.total - mem.available) / mem.total * 100).toFixed(1);
|
||||
console.log('Memory:');
|
||||
console.log(` Total: ${(mem.total / 1024 / 1024).toFixed(2)} GB`);
|
||||
console.log(` Available: ${(mem.available / 1024 / 1024).toFixed(2)} GB`);
|
||||
console.log(` Used: ${usedPercent}%`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Load Average
|
||||
const load = await fetchMetric('/api/load');
|
||||
if (load) {
|
||||
console.log('Load Average:');
|
||||
console.log(` 1min: ${load.one.toFixed(2)}`);
|
||||
console.log(` 5min: ${load.five.toFixed(2)}`);
|
||||
console.log(` 15min: ${load.fifteen.toFixed(2)}`);
|
||||
console.log(` Processes: ${load.runningProcesses}/${load.totalProcesses}`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Network Stats
|
||||
const net = await fetchMetric('/api/network');
|
||||
if (net && net.length > 0) {
|
||||
console.log('Network Interfaces:');
|
||||
net.slice(0, 3).forEach(iface => {
|
||||
console.log(` ${iface.interface}:`);
|
||||
console.log(` RX: ${(iface.rxBytes / 1024 / 1024).toFixed(2)} MB | TX: ${(iface.txBytes / 1024 / 1024).toFixed(2)} MB`);
|
||||
});
|
||||
}
|
||||
console.log();
|
||||
|
||||
console.log('Press Ctrl+C to exit');
|
||||
console.log(`Last update: ${new Date().toLocaleTimeString()}`);
|
||||
}
|
||||
|
||||
function startMonitoring() {
|
||||
// Initial display
|
||||
displayMetrics();
|
||||
|
||||
// Update every 2 seconds
|
||||
setInterval(displayMetrics, 2000);
|
||||
}
|
||||
|
||||
// Handle cleanup
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\nClosing connection...');
|
||||
eventSource.close();
|
||||
process.exit(0);
|
||||
});
|
||||
97
examples/python-client.py
Archivo normal
97
examples/python-client.py
Archivo normal
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Example: Using MCP ProcFS Server HTTP API from Python
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from typing import Dict, Any
|
||||
|
||||
BASE_URL = "http://localhost:3000"
|
||||
|
||||
def pretty_print(data: Any) -> None:
|
||||
"""Pretty print JSON data"""
|
||||
print(json.dumps(data, indent=2))
|
||||
|
||||
def get_cpu_info() -> Dict:
|
||||
"""Get CPU information"""
|
||||
response = requests.get(f"{BASE_URL}/api/cpu")
|
||||
return response.json()
|
||||
|
||||
def get_memory_info() -> Dict:
|
||||
"""Get memory information"""
|
||||
response = requests.get(f"{BASE_URL}/api/memory")
|
||||
return response.json()
|
||||
|
||||
def get_load_average() -> Dict:
|
||||
"""Get system load average"""
|
||||
response = requests.get(f"{BASE_URL}/api/load")
|
||||
return response.json()
|
||||
|
||||
def get_network_stats(interface: str = None) -> Dict:
|
||||
"""Get network statistics"""
|
||||
params = {"interface": interface} if interface else {}
|
||||
response = requests.get(f"{BASE_URL}/api/network", params=params)
|
||||
return response.json()
|
||||
|
||||
def get_process_info(pid: int) -> Dict:
|
||||
"""Get process information"""
|
||||
response = requests.get(f"{BASE_URL}/api/processes/{pid}")
|
||||
return response.json()
|
||||
|
||||
def read_sysctl(key: str) -> Dict:
|
||||
"""Read sysctl parameter"""
|
||||
response = requests.get(f"{BASE_URL}/api/sysctl/{key}")
|
||||
return response.json()
|
||||
|
||||
def write_sysctl(key: str, value) -> Dict:
|
||||
"""Write sysctl parameter"""
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/api/sysctl",
|
||||
json={"key": key, "value": value}
|
||||
)
|
||||
return response.json()
|
||||
|
||||
def main():
|
||||
print("=== MCP ProcFS Server Python Client Example ===\n")
|
||||
|
||||
# Get CPU info
|
||||
print("CPU Information:")
|
||||
cpu_info = get_cpu_info()
|
||||
pretty_print(cpu_info)
|
||||
print()
|
||||
|
||||
# Get memory info
|
||||
print("Memory Information:")
|
||||
mem_info = get_memory_info()
|
||||
pretty_print(mem_info)
|
||||
print()
|
||||
|
||||
# Get load average
|
||||
print("Load Average:")
|
||||
load_avg = get_load_average()
|
||||
pretty_print(load_avg)
|
||||
print()
|
||||
|
||||
# Get network stats for eth0
|
||||
print("Network Statistics:")
|
||||
net_stats = get_network_stats()
|
||||
pretty_print(net_stats)
|
||||
print()
|
||||
|
||||
# Get process info for PID 1
|
||||
print("Process Info (PID 1):")
|
||||
proc_info = get_process_info(1)
|
||||
pretty_print(proc_info)
|
||||
print()
|
||||
|
||||
# Read sysctl parameter
|
||||
print("Sysctl Parameter (kernel.hostname):")
|
||||
sysctl_info = read_sysctl("kernel.hostname")
|
||||
pretty_print(sysctl_info)
|
||||
print()
|
||||
|
||||
print("=== Example Complete ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
22
jest.config.js
Archivo normal
22
jest.config.js
Archivo normal
@@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/src', '<rootDir>/tests'],
|
||||
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.ts',
|
||||
'!src/**/*.d.ts',
|
||||
'!src/**/*.test.ts',
|
||||
'!src/**/__tests__/**'
|
||||
],
|
||||
coverageDirectory: 'coverage',
|
||||
coverageReporters: ['text', 'lcov', 'html'],
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: {
|
||||
esModuleInterop: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
7083
package-lock.json
generado
Archivo normal
7083
package-lock.json
generado
Archivo normal
La diferencia del archivo ha sido suprimido porque es demasiado grande
Cargar Diff
72
package.json
Archivo normal
72
package.json
Archivo normal
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "@mcp/procfs-server",
|
||||
"version": "1.0.0",
|
||||
"description": "MCP server for reading and modifying Linux procfs values with JSON-RPC and SSE support",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"bin": {
|
||||
"mcp-procfs": "./dist/cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsx watch src/cli.ts",
|
||||
"start": "node dist/cli.js",
|
||||
"start:sse": "node dist/server-sse.js",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"format": "prettier --write \"src/**/*.ts\"",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
"prepare": "npm run build",
|
||||
"docs": "typedoc --out docs src"
|
||||
},
|
||||
"keywords": [
|
||||
"mcp",
|
||||
"model-context-protocol",
|
||||
"procfs",
|
||||
"linux",
|
||||
"system-monitoring",
|
||||
"json-rpc",
|
||||
"sse",
|
||||
"server-sent-events"
|
||||
],
|
||||
"author": "MCP Contributors",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^0.5.0",
|
||||
"express": "^4.18.2",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
"zod": "^3.22.4",
|
||||
"cors": "^2.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^20.10.6",
|
||||
"@types/swagger-jsdoc": "^6.0.4",
|
||||
"@types/swagger-ui-express": "^4.1.6",
|
||||
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||
"@typescript-eslint/parser": "^6.17.0",
|
||||
"eslint": "^8.56.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.1",
|
||||
"ts-jest": "^29.1.1",
|
||||
"tsx": "^4.7.0",
|
||||
"typedoc": "^0.25.6",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cameronrye/activitypub-mcp.git",
|
||||
"directory": "mcp-proc"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/cameronrye/activitypub-mcp/issues"
|
||||
},
|
||||
"homepage": "https://github.com/cameronrye/activitypub-mcp/tree/master/mcp-proc#readme"
|
||||
}
|
||||
17
scripts/build.sh
Archivo ejecutable
17
scripts/build.sh
Archivo ejecutable
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "🔨 Building MCP ProcFS Server..."
|
||||
|
||||
# Clean previous build
|
||||
rm -rf dist/
|
||||
|
||||
# Compile TypeScript
|
||||
tsc
|
||||
|
||||
# Make CLI files executable
|
||||
chmod +x dist/cli.js
|
||||
chmod +x dist/cli-sse.js
|
||||
|
||||
echo "✅ Build complete!"
|
||||
echo "Output: dist/"
|
||||
31
scripts/release.sh
Archivo ejecutable
31
scripts/release.sh
Archivo ejecutable
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
VERSION=$1
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "Usage: ./scripts/release.sh <version>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🚀 Releasing version $VERSION..."
|
||||
|
||||
# Run tests
|
||||
echo "Running tests..."
|
||||
npm test
|
||||
|
||||
# Build
|
||||
echo "Building..."
|
||||
npm run build
|
||||
|
||||
# Update version
|
||||
npm version "$VERSION" -m "Release v%s"
|
||||
|
||||
# Publish
|
||||
echo "Publishing to npm..."
|
||||
npm publish
|
||||
|
||||
# Push tags
|
||||
git push --follow-tags
|
||||
|
||||
echo "✅ Released version $VERSION!"
|
||||
46
scripts/setup.sh
Archivo ejecutable
46
scripts/setup.sh
Archivo ejecutable
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "🚀 Setting up MCP ProcFS Server..."
|
||||
|
||||
# Check Node.js version
|
||||
NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
|
||||
if [ "$NODE_VERSION" -lt 18 ]; then
|
||||
echo "❌ Node.js 18 or higher is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Node.js version check passed"
|
||||
|
||||
# Install dependencies
|
||||
echo "📦 Installing dependencies..."
|
||||
npm install
|
||||
|
||||
# Build the project
|
||||
echo "🔨 Building TypeScript..."
|
||||
npm run build
|
||||
|
||||
# Check if running on Linux
|
||||
if [ "$(uname)" != "Linux" ]; then
|
||||
echo "⚠️ Warning: This server is designed for Linux systems"
|
||||
echo " Some features may not work on $(uname)"
|
||||
fi
|
||||
|
||||
# Check permissions
|
||||
if [ ! -r /proc/cpuinfo ]; then
|
||||
echo "❌ Cannot read /proc filesystem"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ /proc filesystem accessible"
|
||||
|
||||
echo ""
|
||||
echo "✅ Setup complete!"
|
||||
echo ""
|
||||
echo "To start the server:"
|
||||
echo " npm start # JSON-RPC server (stdio)"
|
||||
echo " npm run start:sse # HTTP server with SSE"
|
||||
echo ""
|
||||
echo "For development:"
|
||||
echo " npm run dev # Watch mode"
|
||||
echo ""
|
||||
104
scripts/verify-install.js
Archivo ejecutable
104
scripts/verify-install.js
Archivo ejecutable
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Installation verification script
|
||||
*/
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
console.log('🔍 Verifying MCP ProcFS Server Installation...\n');
|
||||
|
||||
const checks = [];
|
||||
|
||||
// Check Node.js version
|
||||
try {
|
||||
const nodeVersion = process.version;
|
||||
const majorVersion = parseInt(nodeVersion.split('.')[0].replace('v', ''));
|
||||
if (majorVersion >= 18) {
|
||||
checks.push({ name: 'Node.js version', status: '✓', message: nodeVersion });
|
||||
} else {
|
||||
checks.push({ name: 'Node.js version', status: '✗', message: `${nodeVersion} (requires >= 18)` });
|
||||
}
|
||||
} catch (error) {
|
||||
checks.push({ name: 'Node.js version', status: '✗', message: error.message });
|
||||
}
|
||||
|
||||
// Check if on Linux
|
||||
const platform = process.platform;
|
||||
if (platform === 'linux') {
|
||||
checks.push({ name: 'Operating System', status: '✓', message: 'Linux' });
|
||||
} else {
|
||||
checks.push({ name: 'Operating System', status: '⚠', message: `${platform} (designed for Linux)` });
|
||||
}
|
||||
|
||||
// Check procfs accessibility
|
||||
try {
|
||||
fs.accessSync('/proc/cpuinfo', fs.constants.R_OK);
|
||||
checks.push({ name: '/proc filesystem', status: '✓', message: 'Accessible' });
|
||||
} catch (error) {
|
||||
checks.push({ name: '/proc filesystem', status: '✗', message: 'Not accessible' });
|
||||
}
|
||||
|
||||
// Check if built
|
||||
const distPath = path.join(__dirname, '..', 'dist');
|
||||
if (fs.existsSync(distPath)) {
|
||||
const files = fs.readdirSync(distPath);
|
||||
if (files.length > 0) {
|
||||
checks.push({ name: 'TypeScript build', status: '✓', message: 'Compiled' });
|
||||
} else {
|
||||
checks.push({ name: 'TypeScript build', status: '✗', message: 'Empty dist folder' });
|
||||
}
|
||||
} else {
|
||||
checks.push({ name: 'TypeScript build', status: '✗', message: 'Run npm run build' });
|
||||
}
|
||||
|
||||
// Check dependencies
|
||||
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
const nodeModulesPath = path.join(__dirname, '..', 'node_modules');
|
||||
if (fs.existsSync(nodeModulesPath)) {
|
||||
checks.push({ name: 'Dependencies', status: '✓', message: 'Installed' });
|
||||
} else {
|
||||
checks.push({ name: 'Dependencies', status: '✗', message: 'Run npm install' });
|
||||
}
|
||||
}
|
||||
|
||||
// Check for sysctl (optional)
|
||||
try {
|
||||
execSync('which sysctl', { stdio: 'pipe' });
|
||||
checks.push({ name: 'sysctl utility', status: '✓', message: 'Available' });
|
||||
} catch (error) {
|
||||
checks.push({ name: 'sysctl utility', status: '⚠', message: 'Not found (optional)' });
|
||||
}
|
||||
|
||||
// Print results
|
||||
console.log('Installation Status:');
|
||||
console.log('='.repeat(60));
|
||||
checks.forEach(check => {
|
||||
console.log(`${check.status} ${check.name.padEnd(25)} ${check.message}`);
|
||||
});
|
||||
console.log('='.repeat(60));
|
||||
|
||||
// Summary
|
||||
const passed = checks.filter(c => c.status === '✓').length;
|
||||
const total = checks.length;
|
||||
const warnings = checks.filter(c => c.status === '⚠').length;
|
||||
const failed = checks.filter(c => c.status === '✗').length;
|
||||
|
||||
console.log(`\n✓ Passed: ${passed}/${total}`);
|
||||
if (warnings > 0) console.log(`⚠ Warnings: ${warnings}`);
|
||||
if (failed > 0) console.log(`✗ Failed: ${failed}`);
|
||||
|
||||
if (failed === 0) {
|
||||
console.log('\n✅ Installation verified successfully!');
|
||||
console.log('\nNext steps:');
|
||||
console.log(' npm start # Start JSON-RPC server');
|
||||
console.log(' npm run start:sse # Start HTTP/SSE server');
|
||||
console.log(' npm test # Run tests');
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log('\n❌ Installation has issues. Please fix the failed checks above.');
|
||||
process.exit(1);
|
||||
}
|
||||
13
src/cli-sse.ts
Archivo normal
13
src/cli-sse.ts
Archivo normal
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
import { MCPProcFSServerSSE } from './server-sse.js';
|
||||
|
||||
async function main() {
|
||||
const port = parseInt(process.env.PORT || '3000');
|
||||
const server = new MCPProcFSServerSSE(port);
|
||||
await server.start();
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
12
src/cli.ts
Archivo normal
12
src/cli.ts
Archivo normal
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
import { MCPProcFSServer } from './server.js';
|
||||
|
||||
async function main() {
|
||||
const server = new MCPProcFSServer();
|
||||
await server.run();
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
7
src/index.ts
Archivo normal
7
src/index.ts
Archivo normal
@@ -0,0 +1,7 @@
|
||||
export { MCPProcFSServer } from './server.js';
|
||||
export { MCPProcFSServerSSE } from './server-sse.js';
|
||||
export { ProcFSReader } from './lib/procfs-reader.js';
|
||||
export { ProcFSWriter } from './lib/procfs-writer.js';
|
||||
export * from './types/procfs.js';
|
||||
export * from './types/mcp.js';
|
||||
export * from './types/schemas.js';
|
||||
245
src/lib/procfs-reader.ts
Archivo normal
245
src/lib/procfs-reader.ts
Archivo normal
@@ -0,0 +1,245 @@
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
CPUInfo,
|
||||
MemInfo,
|
||||
LoadAvg,
|
||||
NetDevStats,
|
||||
DiskStats,
|
||||
ProcessInfo,
|
||||
} from '../types/procfs';
|
||||
|
||||
/**
|
||||
* ProcFS Reader - Handles reading from /proc filesystem
|
||||
*/
|
||||
export class ProcFSReader {
|
||||
private procRoot: string;
|
||||
|
||||
constructor(procRoot = '/proc') {
|
||||
this.procRoot = procRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read raw file content from procfs
|
||||
*/
|
||||
async readRaw(filePath: string): Promise<string> {
|
||||
const fullPath = path.join(this.procRoot, filePath);
|
||||
try {
|
||||
const content = await fs.readFile(fullPath, 'utf-8');
|
||||
return content;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to read ${fullPath}: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path is writable
|
||||
*/
|
||||
async isWritable(filePath: string): Promise<boolean> {
|
||||
const fullPath = path.join(this.procRoot, filePath);
|
||||
try {
|
||||
await fs.access(fullPath, fs.constants.W_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CPU information from /proc/cpuinfo
|
||||
*/
|
||||
async getCPUInfo(): Promise<CPUInfo> {
|
||||
const content = await this.readRaw('cpuinfo');
|
||||
const lines = content.split('\n');
|
||||
|
||||
let model = '';
|
||||
let cores = 0;
|
||||
let processors = 0;
|
||||
let mhz = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('model name')) {
|
||||
if (!model) model = line.split(':')[1].trim();
|
||||
} else if (line.startsWith('cpu cores')) {
|
||||
cores = parseInt(line.split(':')[1].trim());
|
||||
} else if (line.startsWith('processor')) {
|
||||
processors++;
|
||||
} else if (line.startsWith('cpu MHz')) {
|
||||
mhz = parseFloat(line.split(':')[1].trim());
|
||||
}
|
||||
}
|
||||
|
||||
return { model, cores: cores || processors, processors, mhz };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory information from /proc/meminfo
|
||||
*/
|
||||
async getMemInfo(): Promise<MemInfo> {
|
||||
const content = await this.readRaw('meminfo');
|
||||
const lines = content.split('\n');
|
||||
const memInfo: Record<string, number> = {};
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^(\w+):\s+(\d+)/);
|
||||
if (match) {
|
||||
memInfo[match[1]] = parseInt(match[2]);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
total: memInfo['MemTotal'] || 0,
|
||||
free: memInfo['MemFree'] || 0,
|
||||
available: memInfo['MemAvailable'] || 0,
|
||||
buffers: memInfo['Buffers'] || 0,
|
||||
cached: memInfo['Cached'] || 0,
|
||||
swapTotal: memInfo['SwapTotal'] || 0,
|
||||
swapFree: memInfo['SwapFree'] || 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get load average from /proc/loadavg
|
||||
*/
|
||||
async getLoadAvg(): Promise<LoadAvg> {
|
||||
const content = await this.readRaw('loadavg');
|
||||
const parts = content.trim().split(/\s+/);
|
||||
|
||||
const [running, total] = parts[3].split('/').map(Number);
|
||||
|
||||
return {
|
||||
one: parseFloat(parts[0]),
|
||||
five: parseFloat(parts[1]),
|
||||
fifteen: parseFloat(parts[2]),
|
||||
runningProcesses: running,
|
||||
totalProcesses: total,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get network device statistics from /proc/net/dev
|
||||
*/
|
||||
async getNetDevStats(interfaceName?: string): Promise<NetDevStats[]> {
|
||||
const content = await this.readRaw('net/dev');
|
||||
const lines = content.split('\n').slice(2); // Skip header lines
|
||||
const stats: NetDevStats[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.trim()) continue;
|
||||
|
||||
const parts = line.trim().split(/\s+/);
|
||||
const iface = parts[0].replace(':', '');
|
||||
|
||||
if (interfaceName && iface !== interfaceName) continue;
|
||||
|
||||
stats.push({
|
||||
interface: iface,
|
||||
rxBytes: parseInt(parts[1]),
|
||||
rxPackets: parseInt(parts[2]),
|
||||
rxErrors: parseInt(parts[3]),
|
||||
rxDropped: parseInt(parts[4]),
|
||||
txBytes: parseInt(parts[9]),
|
||||
txPackets: parseInt(parts[10]),
|
||||
txErrors: parseInt(parts[11]),
|
||||
txDropped: parseInt(parts[12]),
|
||||
});
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disk statistics from /proc/diskstats
|
||||
*/
|
||||
async getDiskStats(deviceName?: string): Promise<DiskStats[]> {
|
||||
const content = await this.readRaw('diskstats');
|
||||
const lines = content.split('\n');
|
||||
const stats: DiskStats[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.trim()) continue;
|
||||
|
||||
const parts = line.trim().split(/\s+/);
|
||||
const device = parts[2];
|
||||
|
||||
if (deviceName && device !== deviceName) continue;
|
||||
|
||||
stats.push({
|
||||
device,
|
||||
readsCompleted: parseInt(parts[3]),
|
||||
readsMerged: parseInt(parts[4]),
|
||||
sectorsRead: parseInt(parts[5]),
|
||||
timeReading: parseInt(parts[6]),
|
||||
writesCompleted: parseInt(parts[7]),
|
||||
writesMerged: parseInt(parts[8]),
|
||||
sectorsWritten: parseInt(parts[9]),
|
||||
timeWriting: parseInt(parts[10]),
|
||||
});
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get process information from /proc/[pid]/
|
||||
*/
|
||||
async getProcessInfo(pid: number): Promise<ProcessInfo> {
|
||||
try {
|
||||
const statContent = await this.readRaw(`${pid}/stat`);
|
||||
const statusContent = await this.readRaw(`${pid}/status`);
|
||||
|
||||
// Parse stat file
|
||||
const statMatch = statContent.match(/^(\d+)\s+\((.+)\)\s+(\w)\s+(\d+)/);
|
||||
if (!statMatch) {
|
||||
throw new Error('Invalid stat format');
|
||||
}
|
||||
|
||||
const [, , name, state, ppid] = statMatch;
|
||||
|
||||
// Parse status file
|
||||
const statusLines = statusContent.split('\n');
|
||||
let threads = 0;
|
||||
let vmSize = 0;
|
||||
let vmRss = 0;
|
||||
|
||||
for (const line of statusLines) {
|
||||
if (line.startsWith('Threads:')) {
|
||||
threads = parseInt(line.split(':')[1].trim());
|
||||
} else if (line.startsWith('VmSize:')) {
|
||||
vmSize = parseInt(line.split(':')[1].trim());
|
||||
} else if (line.startsWith('VmRSS:')) {
|
||||
vmRss = parseInt(line.split(':')[1].trim());
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pid,
|
||||
name,
|
||||
state,
|
||||
ppid: parseInt(ppid),
|
||||
threads,
|
||||
vmSize,
|
||||
vmRss,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get process info for PID ${pid}: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all PIDs
|
||||
*/
|
||||
async listPIDs(): Promise<number[]> {
|
||||
const entries = await fs.readdir(this.procRoot);
|
||||
const pids: number[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const pid = parseInt(entry);
|
||||
if (!isNaN(pid)) {
|
||||
pids.push(pid);
|
||||
}
|
||||
}
|
||||
|
||||
return pids.sort((a, b) => a - b);
|
||||
}
|
||||
}
|
||||
157
src/lib/procfs-writer.ts
Archivo normal
157
src/lib/procfs-writer.ts
Archivo normal
@@ -0,0 +1,157 @@
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { SysctlParam } from '../types/procfs';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
/**
|
||||
* ProcFS Writer - Handles writing to /proc filesystem and sysctl
|
||||
*/
|
||||
export class ProcFSWriter {
|
||||
private procRoot: string;
|
||||
|
||||
constructor(procRoot = '/proc') {
|
||||
this.procRoot = procRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write value to a procfs file
|
||||
*/
|
||||
async writeRaw(filePath: string, value: string | number): Promise<void> {
|
||||
const fullPath = path.join(this.procRoot, filePath);
|
||||
try {
|
||||
await fs.writeFile(fullPath, String(value), 'utf-8');
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to write to ${fullPath}: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read sysctl parameter
|
||||
*/
|
||||
async readSysctl(key: string): Promise<SysctlParam> {
|
||||
try {
|
||||
const { stdout } = await execAsync(`sysctl -n ${key}`);
|
||||
const value = stdout.trim();
|
||||
|
||||
// Try to parse as number
|
||||
const numValue = parseFloat(value);
|
||||
const finalValue = isNaN(numValue) ? value : numValue;
|
||||
|
||||
// Check if writable by attempting to read the file
|
||||
const sysPath = `/proc/sys/${key.replace(/\./g, '/')}`;
|
||||
let writable = false;
|
||||
try {
|
||||
await fs.access(sysPath, fs.constants.W_OK);
|
||||
writable = true;
|
||||
} catch {
|
||||
writable = false;
|
||||
}
|
||||
|
||||
return {
|
||||
key,
|
||||
value: finalValue,
|
||||
writable,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to read sysctl ${key}: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write sysctl parameter
|
||||
*/
|
||||
async writeSysctl(key: string, value: string | number): Promise<SysctlParam> {
|
||||
try {
|
||||
await execAsync(`sysctl -w ${key}=${value}`);
|
||||
return await this.readSysctl(key);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to write sysctl ${key}: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all sysctl parameters
|
||||
*/
|
||||
async listSysctl(): Promise<SysctlParam[]> {
|
||||
try {
|
||||
const { stdout } = await execAsync('sysctl -a');
|
||||
const lines = stdout.split('\n');
|
||||
const params: SysctlParam[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.trim()) continue;
|
||||
const match = line.match(/^([^\s=]+)\s*=\s*(.+)$/);
|
||||
if (match) {
|
||||
const key = match[1].trim();
|
||||
const value = match[2].trim();
|
||||
const numValue = parseFloat(value);
|
||||
|
||||
params.push({
|
||||
key,
|
||||
value: isNaN(numValue) ? value : numValue,
|
||||
writable: false, // Would need to check each individually
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to list sysctl parameters: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set process priority (nice value)
|
||||
*/
|
||||
async setProcessPriority(pid: number, priority: number): Promise<void> {
|
||||
if (priority < -20 || priority > 19) {
|
||||
throw new Error('Priority must be between -20 and 19');
|
||||
}
|
||||
|
||||
try {
|
||||
await execAsync(`renice ${priority} -p ${pid}`);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to set priority for PID ${pid}: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set CPU affinity for a process
|
||||
*/
|
||||
async setProcessAffinity(pid: number, cpuList: string): Promise<void> {
|
||||
try {
|
||||
await execAsync(`taskset -cp ${cpuList} ${pid}`);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to set CPU affinity for PID ${pid}: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send signal to a process
|
||||
*/
|
||||
async sendSignal(pid: number, signal: string | number = 'TERM'): Promise<void> {
|
||||
try {
|
||||
await execAsync(`kill -${signal} ${pid}`);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to send signal ${signal} to PID ${pid}: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set OOM score adjustment for a process
|
||||
*/
|
||||
async setOOMScore(pid: number, score: number): Promise<void> {
|
||||
if (score < -1000 || score > 1000) {
|
||||
throw new Error('OOM score must be between -1000 and 1000');
|
||||
}
|
||||
|
||||
try {
|
||||
await this.writeRaw(`${pid}/oom_score_adj`, score);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to set OOM score for PID ${pid}: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
636
src/server-sse.ts
Archivo normal
636
src/server-sse.ts
Archivo normal
@@ -0,0 +1,636 @@
|
||||
import express, { Response } from 'express';
|
||||
import cors from 'cors';
|
||||
import swaggerJsdoc from 'swagger-jsdoc';
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
import { ProcFSReader } from './lib/procfs-reader.js';
|
||||
import { ProcFSWriter } from './lib/procfs-writer.js';
|
||||
import {
|
||||
ProcFSReadRequestSchema,
|
||||
ProcFSWriteRequestSchema,
|
||||
SysctlWriteRequestSchema,
|
||||
} from './types/schemas.js';
|
||||
import { MCPRequest, MCPResponse, MCPErrorCode } from './types/mcp.js';
|
||||
|
||||
/**
|
||||
* MCP ProcFS Server with SSE support
|
||||
*/
|
||||
export class MCPProcFSServerSSE {
|
||||
private app: express.Application;
|
||||
private reader: ProcFSReader;
|
||||
private writer: ProcFSWriter;
|
||||
private port: number;
|
||||
private clients: Map<string, Response> = new Map();
|
||||
|
||||
constructor(port = 3000) {
|
||||
this.app = express();
|
||||
this.reader = new ProcFSReader();
|
||||
this.writer = new ProcFSWriter();
|
||||
this.port = port;
|
||||
|
||||
this.setupMiddleware();
|
||||
this.setupSwagger();
|
||||
this.setupRoutes();
|
||||
}
|
||||
|
||||
private setupMiddleware() {
|
||||
this.app.use(cors());
|
||||
this.app.use(express.json());
|
||||
|
||||
// Request logging
|
||||
this.app.use((req, _res, next) => {
|
||||
console.log(`${req.method} ${req.path}`);
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
private setupSwagger() {
|
||||
const swaggerDefinition = {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'MCP ProcFS Server API',
|
||||
version: '1.0.0',
|
||||
description: 'Model Context Protocol server for reading and modifying Linux procfs values',
|
||||
contact: {
|
||||
name: 'MCP Contributors',
|
||||
},
|
||||
license: {
|
||||
name: 'MIT',
|
||||
url: 'https://opensource.org/licenses/MIT',
|
||||
},
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: `http://localhost:${this.port}`,
|
||||
description: 'Development server',
|
||||
},
|
||||
],
|
||||
tags: [
|
||||
{
|
||||
name: 'System Information',
|
||||
description: 'Endpoints for reading system information',
|
||||
},
|
||||
{
|
||||
name: 'ProcFS',
|
||||
description: 'Direct procfs file operations',
|
||||
},
|
||||
{
|
||||
name: 'Sysctl',
|
||||
description: 'Kernel parameter management',
|
||||
},
|
||||
{
|
||||
name: 'Process Management',
|
||||
description: 'Process control and monitoring',
|
||||
},
|
||||
{
|
||||
name: 'MCP',
|
||||
description: 'Model Context Protocol endpoints',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const options = {
|
||||
swaggerDefinition,
|
||||
apis: ['./src/server-sse.ts', './dist/server-sse.js'],
|
||||
};
|
||||
|
||||
const swaggerSpec = swaggerJsdoc(options);
|
||||
this.app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
||||
this.app.get('/api-docs.json', (_req, res) => {
|
||||
res.json(swaggerSpec);
|
||||
});
|
||||
}
|
||||
|
||||
private setupRoutes() {
|
||||
/**
|
||||
* @swagger
|
||||
* /health:
|
||||
* get:
|
||||
* summary: Health check endpoint
|
||||
* tags: [System Information]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Server is healthy
|
||||
*/
|
||||
this.app.get('/health', (_req, res) => {
|
||||
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /mcp/sse:
|
||||
* get:
|
||||
* summary: Server-Sent Events endpoint for MCP
|
||||
* tags: [MCP]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: SSE stream established
|
||||
* content:
|
||||
* text/event-stream:
|
||||
* schema:
|
||||
* type: string
|
||||
*/
|
||||
this.app.get('/mcp/sse', (req, res) => {
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Connection', 'keep-alive');
|
||||
|
||||
const clientId = `client_${Date.now()}_${Math.random()}`;
|
||||
this.clients.set(clientId, res);
|
||||
|
||||
// Send initial connection message
|
||||
this.sendSSE(res, 'connected', { clientId, timestamp: new Date().toISOString() });
|
||||
|
||||
req.on('close', () => {
|
||||
this.clients.delete(clientId);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /mcp/rpc:
|
||||
* post:
|
||||
* summary: JSON-RPC endpoint for MCP
|
||||
* tags: [MCP]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* jsonrpc:
|
||||
* type: string
|
||||
* example: "2.0"
|
||||
* id:
|
||||
* type: string
|
||||
* method:
|
||||
* type: string
|
||||
* params:
|
||||
* type: object
|
||||
* responses:
|
||||
* 200:
|
||||
* description: JSON-RPC response
|
||||
*/
|
||||
this.app.post('/mcp/rpc', async (req, res) => {
|
||||
const mcpRequest = req.body as MCPRequest;
|
||||
const response = await this.handleMCPRequest(mcpRequest);
|
||||
res.json(response);
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cpu:
|
||||
* get:
|
||||
* summary: Get CPU information
|
||||
* tags: [System Information]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: CPU information
|
||||
*/
|
||||
this.app.get('/api/cpu', async (_req, res) => {
|
||||
try {
|
||||
const info = await this.reader.getCPUInfo();
|
||||
res.json({ success: true, data: info });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/memory:
|
||||
* get:
|
||||
* summary: Get memory information
|
||||
* tags: [System Information]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Memory information
|
||||
*/
|
||||
this.app.get('/api/memory', async (_req, res) => {
|
||||
try {
|
||||
const info = await this.reader.getMemInfo();
|
||||
res.json({ success: true, data: info });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/load:
|
||||
* get:
|
||||
* summary: Get system load average
|
||||
* tags: [System Information]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Load average information
|
||||
*/
|
||||
this.app.get('/api/load', async (_req, res) => {
|
||||
try {
|
||||
const info = await this.reader.getLoadAvg();
|
||||
res.json({ success: true, data: info });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/network:
|
||||
* get:
|
||||
* summary: Get network interface statistics
|
||||
* tags: [System Information]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: interface
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Specific interface name
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Network statistics
|
||||
*/
|
||||
this.app.get('/api/network', async (req, res) => {
|
||||
try {
|
||||
const iface = req.query.interface as string | undefined;
|
||||
const stats = await this.reader.getNetDevStats(iface);
|
||||
res.json({ success: true, data: stats });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/disk:
|
||||
* get:
|
||||
* summary: Get disk I/O statistics
|
||||
* tags: [System Information]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: device
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Specific device name
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Disk statistics
|
||||
*/
|
||||
this.app.get('/api/disk', async (req, res) => {
|
||||
try {
|
||||
const device = req.query.device as string | undefined;
|
||||
const stats = await this.reader.getDiskStats(device);
|
||||
res.json({ success: true, data: stats });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/procfs:
|
||||
* get:
|
||||
* summary: Read a procfs file
|
||||
* tags: [ProcFS]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: path
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Path relative to /proc
|
||||
* - in: query
|
||||
* name: format
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [raw, parsed]
|
||||
* description: Return format
|
||||
* responses:
|
||||
* 200:
|
||||
* description: File contents
|
||||
*/
|
||||
this.app.get('/api/procfs', async (req, res) => {
|
||||
try {
|
||||
const validated = ProcFSReadRequestSchema.parse(req.query);
|
||||
const content = await this.reader.readRaw(validated.path);
|
||||
res.json({ success: true, data: content });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/procfs:
|
||||
* post:
|
||||
* summary: Write to a procfs file
|
||||
* tags: [ProcFS]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* path:
|
||||
* type: string
|
||||
* value:
|
||||
* oneOf:
|
||||
* - type: string
|
||||
* - type: number
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Write successful
|
||||
*/
|
||||
this.app.post('/api/procfs', async (req, res) => {
|
||||
try {
|
||||
const validated = ProcFSWriteRequestSchema.parse(req.body);
|
||||
await this.writer.writeRaw(validated.path, validated.value);
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/sysctl/{key}:
|
||||
* get:
|
||||
* summary: Read a sysctl parameter
|
||||
* tags: [Sysctl]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: key
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Sysctl key
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Sysctl parameter value
|
||||
*/
|
||||
this.app.get('/api/sysctl/:key', async (req, res) => {
|
||||
try {
|
||||
const param = await this.writer.readSysctl(req.params.key);
|
||||
res.json({ success: true, data: param });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/sysctl:
|
||||
* post:
|
||||
* summary: Write a sysctl parameter
|
||||
* tags: [Sysctl]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* key:
|
||||
* type: string
|
||||
* value:
|
||||
* oneOf:
|
||||
* - type: string
|
||||
* - type: number
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Sysctl parameter updated
|
||||
*/
|
||||
this.app.post('/api/sysctl', async (req, res) => {
|
||||
try {
|
||||
const validated = SysctlWriteRequestSchema.parse(req.body);
|
||||
const param = await this.writer.writeSysctl(validated.key, validated.value);
|
||||
res.json({ success: true, data: param });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/sysctl:
|
||||
* get:
|
||||
* summary: List all sysctl parameters
|
||||
* tags: [Sysctl]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: List of sysctl parameters
|
||||
*/
|
||||
this.app.get('/api/sysctl', async (_req, res) => {
|
||||
try {
|
||||
const params = await this.writer.listSysctl();
|
||||
res.json({ success: true, data: params });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/processes:
|
||||
* get:
|
||||
* summary: List all process IDs
|
||||
* tags: [Process Management]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: List of PIDs
|
||||
*/
|
||||
this.app.get('/api/processes', async (_req, res) => {
|
||||
try {
|
||||
const pids = await this.reader.listPIDs();
|
||||
res.json({ success: true, data: pids });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/processes/{pid}:
|
||||
* get:
|
||||
* summary: Get process information
|
||||
* tags: [Process Management]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: pid
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: Process ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Process information
|
||||
*/
|
||||
this.app.get('/api/processes/:pid', async (req, res) => {
|
||||
try {
|
||||
const pid = parseInt(req.params.pid);
|
||||
const info = await this.reader.getProcessInfo(pid);
|
||||
res.json({ success: true, data: info });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/processes/{pid}/priority:
|
||||
* post:
|
||||
* summary: Set process priority
|
||||
* tags: [Process Management]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: pid
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* priority:
|
||||
* type: number
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Priority updated
|
||||
*/
|
||||
this.app.post('/api/processes/:pid/priority', async (req, res) => {
|
||||
try {
|
||||
const pid = parseInt(req.params.pid);
|
||||
const { priority } = req.body;
|
||||
await this.writer.setProcessPriority(pid, priority);
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private sendSSE(res: Response, event: string, data: unknown) {
|
||||
res.write(`event: ${event}\n`);
|
||||
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
||||
}
|
||||
|
||||
private async handleMCPRequest(request: MCPRequest): Promise<MCPResponse> {
|
||||
try {
|
||||
const { method, params, id } = request;
|
||||
|
||||
let result;
|
||||
|
||||
switch (method) {
|
||||
case 'tools/list':
|
||||
result = await this.listTools();
|
||||
break;
|
||||
case 'tools/call':
|
||||
result = await this.callTool(params?.name as string, params?.arguments as Record<string, unknown>);
|
||||
break;
|
||||
case 'resources/list':
|
||||
result = await this.listResources();
|
||||
break;
|
||||
case 'resources/read':
|
||||
result = await this.readResource(params?.uri as string);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown method: ${method}`);
|
||||
}
|
||||
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
id,
|
||||
result,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
id: request.id,
|
||||
error: {
|
||||
code: MCPErrorCode.InternalError,
|
||||
message: (error as Error).message,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async listTools() {
|
||||
return {
|
||||
tools: [
|
||||
{ name: 'read_procfs', description: 'Read from procfs' },
|
||||
{ name: 'write_procfs', description: 'Write to procfs' },
|
||||
{ name: 'get_cpu_info', description: 'Get CPU information' },
|
||||
{ name: 'get_memory_info', description: 'Get memory information' },
|
||||
{ name: 'get_load_average', description: 'Get load average' },
|
||||
{ name: 'get_network_stats', description: 'Get network statistics' },
|
||||
{ name: 'get_disk_stats', description: 'Get disk statistics' },
|
||||
{ name: 'read_sysctl', description: 'Read sysctl parameter' },
|
||||
{ name: 'write_sysctl', description: 'Write sysctl parameter' },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private async callTool(name: string, _args: Record<string, unknown>) {
|
||||
switch (name) {
|
||||
case 'get_cpu_info':
|
||||
return await this.reader.getCPUInfo();
|
||||
case 'get_memory_info':
|
||||
return await this.reader.getMemInfo();
|
||||
case 'get_load_average':
|
||||
return await this.reader.getLoadAvg();
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async listResources() {
|
||||
return {
|
||||
resources: [
|
||||
{ uri: 'procfs://cpuinfo', name: 'CPU Information' },
|
||||
{ uri: 'procfs://meminfo', name: 'Memory Information' },
|
||||
{ uri: 'procfs://loadavg', name: 'Load Average' },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private async readResource(uri: string) {
|
||||
const path = uri.replace('procfs://', '');
|
||||
let data;
|
||||
|
||||
if (path === 'cpuinfo') {
|
||||
data = await this.reader.getCPUInfo();
|
||||
} else if (path === 'meminfo') {
|
||||
data = await this.reader.getMemInfo();
|
||||
} else if (path === 'loadavg') {
|
||||
data = await this.reader.getLoadAvg();
|
||||
} else {
|
||||
throw new Error(`Unknown resource: ${uri}`);
|
||||
}
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri,
|
||||
mimeType: 'application/json',
|
||||
text: JSON.stringify(data, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
async start() {
|
||||
return new Promise<void>((resolve) => {
|
||||
this.app.listen(this.port, () => {
|
||||
console.log(`MCP ProcFS Server SSE running on http://localhost:${this.port}`);
|
||||
console.log(`API Documentation: http://localhost:${this.port}/api-docs`);
|
||||
console.log(`SSE Endpoint: http://localhost:${this.port}/mcp/sse`);
|
||||
console.log(`RPC Endpoint: http://localhost:${this.port}/mcp/rpc`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
467
src/server.ts
Archivo normal
467
src/server.ts
Archivo normal
@@ -0,0 +1,467 @@
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
ListResourcesRequestSchema,
|
||||
ReadResourceRequestSchema,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import { ProcFSReader } from './lib/procfs-reader.js';
|
||||
import { ProcFSWriter } from './lib/procfs-writer.js';
|
||||
import {
|
||||
ProcFSReadRequestSchema,
|
||||
ProcFSWriteRequestSchema,
|
||||
SysctlReadRequestSchema,
|
||||
SysctlWriteRequestSchema,
|
||||
ProcessInfoRequestSchema,
|
||||
NetworkInterfaceRequestSchema,
|
||||
DiskStatsRequestSchema,
|
||||
} from './types/schemas.js';
|
||||
|
||||
/**
|
||||
* MCP ProcFS Server - Main server implementation
|
||||
*/
|
||||
export class MCPProcFSServer {
|
||||
private server: Server;
|
||||
private reader: ProcFSReader;
|
||||
private writer: ProcFSWriter;
|
||||
|
||||
constructor() {
|
||||
this.server = new Server(
|
||||
{
|
||||
name: 'procfs-server',
|
||||
version: '1.0.0',
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
resources: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
this.reader = new ProcFSReader();
|
||||
this.writer = new ProcFSWriter();
|
||||
|
||||
this.setupHandlers();
|
||||
}
|
||||
|
||||
private setupHandlers() {
|
||||
// List available tools
|
||||
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: 'read_procfs',
|
||||
description: 'Read a file from the /proc filesystem',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
path: {
|
||||
type: 'string',
|
||||
description: 'Path relative to /proc (e.g., "cpuinfo", "meminfo")',
|
||||
},
|
||||
format: {
|
||||
type: 'string',
|
||||
enum: ['raw', 'parsed'],
|
||||
description: 'Return format: raw text or parsed data',
|
||||
default: 'parsed',
|
||||
},
|
||||
},
|
||||
required: ['path'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'write_procfs',
|
||||
description: 'Write a value to a /proc filesystem file',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
path: {
|
||||
type: 'string',
|
||||
description: 'Path relative to /proc',
|
||||
},
|
||||
value: {
|
||||
type: ['string', 'number'],
|
||||
description: 'Value to write',
|
||||
},
|
||||
},
|
||||
required: ['path', 'value'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'get_cpu_info',
|
||||
description: 'Get detailed CPU information',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'get_memory_info',
|
||||
description: 'Get detailed memory information',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'get_load_average',
|
||||
description: 'Get system load average',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'get_network_stats',
|
||||
description: 'Get network interface statistics',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
interface: {
|
||||
type: 'string',
|
||||
description: 'Specific interface name (optional)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'get_disk_stats',
|
||||
description: 'Get disk I/O statistics',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
device: {
|
||||
type: 'string',
|
||||
description: 'Specific device name (optional)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'get_process_info',
|
||||
description: 'Get information about a specific process',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pid: {
|
||||
type: 'number',
|
||||
description: 'Process ID',
|
||||
},
|
||||
},
|
||||
required: ['pid'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'list_processes',
|
||||
description: 'List all process IDs',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'read_sysctl',
|
||||
description: 'Read a sysctl kernel parameter',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
description: 'Sysctl key (e.g., "net.ipv4.ip_forward")',
|
||||
},
|
||||
},
|
||||
required: ['key'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'write_sysctl',
|
||||
description: 'Write a sysctl kernel parameter',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
description: 'Sysctl key',
|
||||
},
|
||||
value: {
|
||||
type: ['string', 'number'],
|
||||
description: 'Value to set',
|
||||
},
|
||||
},
|
||||
required: ['key', 'value'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'list_sysctl',
|
||||
description: 'List all sysctl parameters',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'set_process_priority',
|
||||
description: 'Set process priority (nice value)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pid: {
|
||||
type: 'number',
|
||||
description: 'Process ID',
|
||||
},
|
||||
priority: {
|
||||
type: 'number',
|
||||
description: 'Nice value (-20 to 19)',
|
||||
},
|
||||
},
|
||||
required: ['pid', 'priority'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'set_process_affinity',
|
||||
description: 'Set CPU affinity for a process',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pid: {
|
||||
type: 'number',
|
||||
description: 'Process ID',
|
||||
},
|
||||
cpuList: {
|
||||
type: 'string',
|
||||
description: 'CPU list (e.g., "0,1" or "0-3")',
|
||||
},
|
||||
},
|
||||
required: ['pid', 'cpuList'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// Handle tool calls
|
||||
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { name, arguments: args } = request.params;
|
||||
|
||||
try {
|
||||
switch (name) {
|
||||
case 'read_procfs': {
|
||||
const validated = ProcFSReadRequestSchema.parse(args);
|
||||
if (validated.format === 'raw') {
|
||||
const content = await this.reader.readRaw(validated.path);
|
||||
return {
|
||||
content: [{ type: 'text', text: content }],
|
||||
};
|
||||
} else {
|
||||
// Try to parse common files
|
||||
let result;
|
||||
if (validated.path === 'cpuinfo') {
|
||||
result = await this.reader.getCPUInfo();
|
||||
} else if (validated.path === 'meminfo') {
|
||||
result = await this.reader.getMemInfo();
|
||||
} else if (validated.path === 'loadavg') {
|
||||
result = await this.reader.getLoadAvg();
|
||||
} else {
|
||||
const content = await this.reader.readRaw(validated.path);
|
||||
return {
|
||||
content: [{ type: 'text', text: content }],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
case 'write_procfs': {
|
||||
const validated = ProcFSWriteRequestSchema.parse(args);
|
||||
await this.writer.writeRaw(validated.path, validated.value);
|
||||
return {
|
||||
content: [{ type: 'text', text: 'Successfully written' }],
|
||||
};
|
||||
}
|
||||
|
||||
case 'get_cpu_info': {
|
||||
const info = await this.reader.getCPUInfo();
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(info, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
case 'get_memory_info': {
|
||||
const info = await this.reader.getMemInfo();
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(info, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
case 'get_load_average': {
|
||||
const info = await this.reader.getLoadAvg();
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(info, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
case 'get_network_stats': {
|
||||
const validated = NetworkInterfaceRequestSchema.parse(args);
|
||||
const stats = await this.reader.getNetDevStats(validated.interface);
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(stats, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
case 'get_disk_stats': {
|
||||
const validated = DiskStatsRequestSchema.parse(args);
|
||||
const stats = await this.reader.getDiskStats(validated.device);
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(stats, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
case 'get_process_info': {
|
||||
const validated = ProcessInfoRequestSchema.parse(args);
|
||||
const info = await this.reader.getProcessInfo(validated.pid);
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(info, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
case 'list_processes': {
|
||||
const pids = await this.reader.listPIDs();
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(pids, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
case 'read_sysctl': {
|
||||
const validated = SysctlReadRequestSchema.parse(args);
|
||||
const param = await this.writer.readSysctl(validated.key);
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(param, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
case 'write_sysctl': {
|
||||
const validated = SysctlWriteRequestSchema.parse(args);
|
||||
const param = await this.writer.writeSysctl(validated.key, validated.value);
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(param, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
case 'list_sysctl': {
|
||||
const params = await this.writer.listSysctl();
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(params, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
case 'set_process_priority': {
|
||||
const { pid, priority } = args as { pid: number; priority: number };
|
||||
await this.writer.setProcessPriority(pid, priority);
|
||||
return {
|
||||
content: [{ type: 'text', text: `Priority set to ${priority} for PID ${pid}` }],
|
||||
};
|
||||
}
|
||||
|
||||
case 'set_process_affinity': {
|
||||
const { pid, cpuList } = args as { pid: number; cpuList: string };
|
||||
await this.writer.setProcessAffinity(pid, cpuList);
|
||||
return {
|
||||
content: [{ type: 'text', text: `CPU affinity set to ${cpuList} for PID ${pid}` }],
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error: ${(error as Error).message}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// List available resources
|
||||
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
return {
|
||||
resources: [
|
||||
{
|
||||
uri: 'procfs://cpuinfo',
|
||||
name: 'CPU Information',
|
||||
description: 'Detailed CPU information from /proc/cpuinfo',
|
||||
mimeType: 'application/json',
|
||||
},
|
||||
{
|
||||
uri: 'procfs://meminfo',
|
||||
name: 'Memory Information',
|
||||
description: 'Memory statistics from /proc/meminfo',
|
||||
mimeType: 'application/json',
|
||||
},
|
||||
{
|
||||
uri: 'procfs://loadavg',
|
||||
name: 'Load Average',
|
||||
description: 'System load average from /proc/loadavg',
|
||||
mimeType: 'application/json',
|
||||
},
|
||||
{
|
||||
uri: 'procfs://net/dev',
|
||||
name: 'Network Statistics',
|
||||
description: 'Network interface statistics',
|
||||
mimeType: 'application/json',
|
||||
},
|
||||
{
|
||||
uri: 'procfs://diskstats',
|
||||
name: 'Disk Statistics',
|
||||
description: 'Disk I/O statistics',
|
||||
mimeType: 'application/json',
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// Read resource contents
|
||||
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||
const uri = request.params.uri;
|
||||
const path = uri.replace('procfs://', '');
|
||||
|
||||
try {
|
||||
let result;
|
||||
if (path === 'cpuinfo') {
|
||||
result = await this.reader.getCPUInfo();
|
||||
} else if (path === 'meminfo') {
|
||||
result = await this.reader.getMemInfo();
|
||||
} else if (path === 'loadavg') {
|
||||
result = await this.reader.getLoadAvg();
|
||||
} else if (path === 'net/dev') {
|
||||
result = await this.reader.getNetDevStats();
|
||||
} else if (path === 'diskstats') {
|
||||
result = await this.reader.getDiskStats();
|
||||
} else {
|
||||
throw new Error(`Unknown resource: ${uri}`);
|
||||
}
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri,
|
||||
mimeType: 'application/json',
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to read resource ${uri}: ${(error as Error).message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async run() {
|
||||
const transport = new StdioServerTransport();
|
||||
await this.server.connect(transport);
|
||||
console.error('MCP ProcFS Server running on stdio');
|
||||
}
|
||||
}
|
||||
103
src/types/mcp.ts
Archivo normal
103
src/types/mcp.ts
Archivo normal
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* MCP Protocol type definitions
|
||||
*/
|
||||
|
||||
export interface MCPRequest {
|
||||
jsonrpc: '2.0';
|
||||
id: string | number;
|
||||
method: string;
|
||||
params?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface MCPResponse {
|
||||
jsonrpc: '2.0';
|
||||
id: string | number;
|
||||
result?: unknown;
|
||||
error?: MCPError;
|
||||
}
|
||||
|
||||
export interface MCPError {
|
||||
code: number;
|
||||
message: string;
|
||||
data?: unknown;
|
||||
}
|
||||
|
||||
export interface MCPNotification {
|
||||
jsonrpc: '2.0';
|
||||
method: string;
|
||||
params?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface MCPToolDefinition {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: {
|
||||
type: 'object';
|
||||
properties: Record<string, unknown>;
|
||||
required?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface MCPResourceDefinition {
|
||||
uri: string;
|
||||
name: string;
|
||||
description: string;
|
||||
mimeType?: string;
|
||||
}
|
||||
|
||||
export interface MCPPromptDefinition {
|
||||
name: string;
|
||||
description: string;
|
||||
arguments?: Array<{
|
||||
name: string;
|
||||
description: string;
|
||||
required: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
export enum MCPErrorCode {
|
||||
ParseError = -32700,
|
||||
InvalidRequest = -32600,
|
||||
MethodNotFound = -32601,
|
||||
InvalidParams = -32602,
|
||||
InternalError = -32603,
|
||||
ServerError = -32000,
|
||||
}
|
||||
|
||||
export interface MCPServerCapabilities {
|
||||
tools?: {
|
||||
listChanged?: boolean;
|
||||
};
|
||||
resources?: {
|
||||
subscribe?: boolean;
|
||||
listChanged?: boolean;
|
||||
};
|
||||
prompts?: {
|
||||
listChanged?: boolean;
|
||||
};
|
||||
experimental?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface MCPInitializeRequest {
|
||||
protocolVersion: string;
|
||||
capabilities: {
|
||||
roots?: {
|
||||
listChanged?: boolean;
|
||||
};
|
||||
sampling?: Record<string, unknown>;
|
||||
experimental?: Record<string, unknown>;
|
||||
};
|
||||
clientInfo: {
|
||||
name: string;
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MCPInitializeResponse {
|
||||
protocolVersion: string;
|
||||
capabilities: MCPServerCapabilities;
|
||||
serverInfo: {
|
||||
name: string;
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
101
src/types/procfs.ts
Archivo normal
101
src/types/procfs.ts
Archivo normal
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Type definitions for MCP ProcFS Server
|
||||
*/
|
||||
|
||||
export interface ProcFSResource {
|
||||
path: string;
|
||||
value: string | number | Record<string, unknown>;
|
||||
writable: boolean;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface CPUInfo {
|
||||
model: string;
|
||||
cores: number;
|
||||
processors: number;
|
||||
mhz: number;
|
||||
}
|
||||
|
||||
export interface MemInfo {
|
||||
total: number;
|
||||
free: number;
|
||||
available: number;
|
||||
buffers: number;
|
||||
cached: number;
|
||||
swapTotal: number;
|
||||
swapFree: number;
|
||||
}
|
||||
|
||||
export interface LoadAvg {
|
||||
one: number;
|
||||
five: number;
|
||||
fifteen: number;
|
||||
runningProcesses: number;
|
||||
totalProcesses: number;
|
||||
}
|
||||
|
||||
export interface NetDevStats {
|
||||
interface: string;
|
||||
rxBytes: number;
|
||||
rxPackets: number;
|
||||
rxErrors: number;
|
||||
rxDropped: number;
|
||||
txBytes: number;
|
||||
txPackets: number;
|
||||
txErrors: number;
|
||||
txDropped: number;
|
||||
}
|
||||
|
||||
export interface DiskStats {
|
||||
device: string;
|
||||
readsCompleted: number;
|
||||
readsMerged: number;
|
||||
sectorsRead: number;
|
||||
timeReading: number;
|
||||
writesCompleted: number;
|
||||
writesMerged: number;
|
||||
sectorsWritten: number;
|
||||
timeWriting: number;
|
||||
}
|
||||
|
||||
export interface ProcessInfo {
|
||||
pid: number;
|
||||
name: string;
|
||||
state: string;
|
||||
ppid: number;
|
||||
threads: number;
|
||||
vmSize: number;
|
||||
vmRss: number;
|
||||
}
|
||||
|
||||
export interface SysctlParam {
|
||||
key: string;
|
||||
value: string | number;
|
||||
writable: boolean;
|
||||
}
|
||||
|
||||
export interface ProcFSReadRequest {
|
||||
path: string;
|
||||
format?: 'raw' | 'parsed';
|
||||
}
|
||||
|
||||
export interface ProcFSWriteRequest {
|
||||
path: string;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
export interface ProcFSResponse<T = unknown> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export type ProcFSResourceType =
|
||||
| 'cpu'
|
||||
| 'memory'
|
||||
| 'load'
|
||||
| 'network'
|
||||
| 'disk'
|
||||
| 'process'
|
||||
| 'sysctl'
|
||||
| 'custom';
|
||||
62
src/types/schemas.ts
Archivo normal
62
src/types/schemas.ts
Archivo normal
@@ -0,0 +1,62 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Zod schemas for validation
|
||||
*/
|
||||
|
||||
export const ProcFSReadRequestSchema = z.object({
|
||||
path: z.string().min(1),
|
||||
format: z.enum(['raw', 'parsed']).optional().default('parsed'),
|
||||
});
|
||||
|
||||
export const ProcFSWriteRequestSchema = z.object({
|
||||
path: z.string().min(1),
|
||||
value: z.union([z.string(), z.number()]),
|
||||
});
|
||||
|
||||
export const SysctlReadRequestSchema = z.object({
|
||||
key: z.string().min(1),
|
||||
});
|
||||
|
||||
export const SysctlWriteRequestSchema = z.object({
|
||||
key: z.string().min(1),
|
||||
value: z.union([z.string(), z.number()]),
|
||||
});
|
||||
|
||||
export const ProcessInfoRequestSchema = z.object({
|
||||
pid: z.number().int().positive(),
|
||||
});
|
||||
|
||||
export const NetworkInterfaceRequestSchema = z.object({
|
||||
interface: z.string().optional(),
|
||||
});
|
||||
|
||||
export const DiskStatsRequestSchema = z.object({
|
||||
device: z.string().optional(),
|
||||
});
|
||||
|
||||
export const MCPRequestSchema = z.object({
|
||||
jsonrpc: z.literal('2.0'),
|
||||
id: z.union([z.string(), z.number()]),
|
||||
method: z.string(),
|
||||
params: z.record(z.unknown()).optional(),
|
||||
});
|
||||
|
||||
export const MCPResponseSchema = z.object({
|
||||
jsonrpc: z.literal('2.0'),
|
||||
id: z.union([z.string(), z.number()]),
|
||||
result: z.unknown().optional(),
|
||||
error: z.object({
|
||||
code: z.number(),
|
||||
message: z.string(),
|
||||
data: z.unknown().optional(),
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
export type ProcFSReadRequestType = z.infer<typeof ProcFSReadRequestSchema>;
|
||||
export type ProcFSWriteRequestType = z.infer<typeof ProcFSWriteRequestSchema>;
|
||||
export type SysctlReadRequestType = z.infer<typeof SysctlReadRequestSchema>;
|
||||
export type SysctlWriteRequestType = z.infer<typeof SysctlWriteRequestSchema>;
|
||||
export type ProcessInfoRequestType = z.infer<typeof ProcessInfoRequestSchema>;
|
||||
export type NetworkInterfaceRequestType = z.infer<typeof NetworkInterfaceRequestSchema>;
|
||||
export type DiskStatsRequestType = z.infer<typeof DiskStatsRequestSchema>;
|
||||
101
tests/procfs-reader.test.ts
Archivo normal
101
tests/procfs-reader.test.ts
Archivo normal
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Basic tests for ProcFS Reader
|
||||
*/
|
||||
|
||||
import { ProcFSReader } from '../src/lib/procfs-reader';
|
||||
|
||||
describe('ProcFSReader', () => {
|
||||
let reader: ProcFSReader;
|
||||
|
||||
beforeEach(() => {
|
||||
reader = new ProcFSReader();
|
||||
});
|
||||
|
||||
describe('System Information', () => {
|
||||
test('should read CPU information', async () => {
|
||||
const info = await reader.getCPUInfo();
|
||||
expect(info).toBeDefined();
|
||||
expect(info).toHaveProperty('model');
|
||||
expect(info).toHaveProperty('cores');
|
||||
expect(info).toHaveProperty('processors');
|
||||
expect(typeof info.cores).toBe('number');
|
||||
expect(info.cores).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('should read memory information', async () => {
|
||||
const info = await reader.getMemInfo();
|
||||
expect(info).toBeDefined();
|
||||
expect(info).toHaveProperty('total');
|
||||
expect(info).toHaveProperty('free');
|
||||
expect(info).toHaveProperty('available');
|
||||
expect(typeof info.total).toBe('number');
|
||||
expect(info.total).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('should read load average', async () => {
|
||||
const info = await reader.getLoadAvg();
|
||||
expect(info).toBeDefined();
|
||||
expect(info).toHaveProperty('one');
|
||||
expect(info).toHaveProperty('five');
|
||||
expect(info).toHaveProperty('fifteen');
|
||||
expect(typeof info.one).toBe('number');
|
||||
expect(info.one).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
test('should read network statistics', async () => {
|
||||
const stats = await reader.getNetDevStats();
|
||||
expect(Array.isArray(stats)).toBe(true);
|
||||
if (stats.length > 0) {
|
||||
const first = stats[0];
|
||||
expect(first).toHaveProperty('interface');
|
||||
expect(first).toHaveProperty('rxBytes');
|
||||
expect(first).toHaveProperty('txBytes');
|
||||
}
|
||||
});
|
||||
|
||||
test('should read disk statistics', async () => {
|
||||
const stats = await reader.getDiskStats();
|
||||
expect(Array.isArray(stats)).toBe(true);
|
||||
if (stats.length > 0) {
|
||||
const first = stats[0];
|
||||
expect(first).toHaveProperty('device');
|
||||
expect(first).toHaveProperty('readsCompleted');
|
||||
expect(first).toHaveProperty('writesCompleted');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Process Information', () => {
|
||||
test('should list PIDs', async () => {
|
||||
const pids = await reader.listPIDs();
|
||||
expect(Array.isArray(pids)).toBe(true);
|
||||
expect(pids.length).toBeGreaterThan(0);
|
||||
expect(pids).toContain(1); // init/systemd should always exist
|
||||
});
|
||||
|
||||
test('should read process information for PID 1', async () => {
|
||||
const info = await reader.getProcessInfo(1);
|
||||
expect(info).toBeDefined();
|
||||
expect(info.pid).toBe(1);
|
||||
expect(info).toHaveProperty('name');
|
||||
expect(info).toHaveProperty('state');
|
||||
expect(info).toHaveProperty('ppid');
|
||||
});
|
||||
|
||||
test('should throw error for non-existent PID', async () => {
|
||||
await expect(reader.getProcessInfo(999999)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Raw File Reading', () => {
|
||||
test('should read raw procfs file', async () => {
|
||||
const content = await reader.readRaw('version');
|
||||
expect(typeof content).toBe('string');
|
||||
expect(content.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('should throw error for non-existent file', async () => {
|
||||
await expect(reader.readRaw('nonexistent')).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
33
tsconfig.json
Archivo normal
33
tsconfig.json
Archivo normal
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"types": ["node", "jest"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
Referencia en una nueva incidencia
Block a user