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