@marcelo-ochoa/server-qnap 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.dockerignore ADDED
@@ -0,0 +1,6 @@
1
+ node_modules
2
+ dist
3
+ *.ts
4
+ tsconfig.json
5
+ Dockerfile
6
+ .dockerignore
package/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [1.0.1] - 2026-02-09
6
+
7
+ ### Changed
8
+ - **Major refactoring for improved maintainability**: Restructured codebase following MySQL MCP server patterns
9
+ - Separated tool handlers into individual files in `tools/` directory (`connect.ts`, `report.ts`, `dir.ts`, `file_info.ts`)
10
+ - Simplified `handlers.ts` to act as a clean dispatcher using a handler registry pattern
11
+ - Refactored `tools.ts` to use array-based tool definitions for consistency
12
+ - Updated `server.ts` to use the new handler dispatcher
13
+ - Improved code organization with better separation of concerns
14
+ - Enhanced error handling and validation in individual tool handlers
15
+ - Added connection state management helper functions
16
+
17
+ ### Added
18
+ - **Prompts array support**: Added prompts capability following MikroTik MCP server pattern
19
+ - Implemented `prompts/list` request handler for better tool discoverability
20
+ - Added prompts for all available tools: `qnap-connect`, `qnap-report`, `qnap-dir`, `qnap-file-info`
21
+ - Enhanced server capabilities to include prompts interface
22
+
23
+ ## [1.0.0] - 2026-01-27
24
+
25
+ ### Added
26
+ - Initial implementation of the QNAP MCP server.
27
+ - Tools: `qnap-connect`, `qnap-report`, `qnap-dir`, `qnap-file-info`.
28
+ - Support for QTS Legacy CGI API.
29
+
package/Demos.md ADDED
@@ -0,0 +1,172 @@
1
+ # Demos
2
+
3
+ Some sample usage scenarios are shown below:
4
+
5
+ ## Usage with Claude Desktop
6
+
7
+ To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`:
8
+
9
+ ### Docker
10
+
11
+ * when running docker on macOS, use `host.docker.internal` if the server is running on the host network (eg localhost)
12
+ * Credentials are passed via environment variables `QNAP_USER` and `QNAP_PASSWORD`
13
+
14
+ ```json
15
+ {
16
+ "mcpServers": {
17
+ "qnap": {
18
+ "command": "docker",
19
+ "args": [
20
+ "run",
21
+ "-i",
22
+ "--rm",
23
+ "-e",
24
+ "QNAP_USER=admin",
25
+ "-e",
26
+ "QNAP_PASSWORD=password",
27
+ "mochoa/mcp-qnap",
28
+ "http://10.1.1.241:8080"]
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ ### NPX
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "qnap": {
40
+ "command": "npx",
41
+ "args": [
42
+ "-y",
43
+ "@marcelo-ochoa/server-qnap",
44
+ "http://10.1.1.241:8080"
45
+ ],
46
+ "env": {
47
+ "QNAP_USER": "admin",
48
+ "QNAP_PASSWORD": "password"
49
+ }
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ ### Demo Prompts
56
+
57
+ Sample prompts using above server.
58
+
59
+ - qnap-connect http://10.1.1.241:8080 admin password
60
+ - qnap-report
61
+ - qnap-dir /Public
62
+ - qnap-file-info /Public/test.txt
63
+ - analyze backup files in several subdirectories of `/backup/stacks` on the QNAP NAS. For each subdirectory compare the two most recent backups (daily or weekly, identified by filenames like `alldb_backup-2.sql.gz` for weekly and `alldb_backup-09.sql.gz` for daily). The comparison should be based on file size, considering backups with similar sizes as correct. Generate a report indicating the status (OK or Error) for each directory based on this comparison.
64
+
65
+ ### Backup Analysis Report Example
66
+
67
+ Busing information about files of sub-folders from qnap-dir /backup/stacks
68
+ - ayudarg
69
+ - glpi
70
+ - invop
71
+ - kanboard
72
+ - mon
73
+ - moodle
74
+ - mygis
75
+ - ocular-vet
76
+ - patogis
77
+ - piexa
78
+ - web
79
+ - yact
80
+ files into above sub-dirs are backups daily or weekly, for example: alldb_backup-2.sql.gz (meaning last backup of week 2 of current month) alldb_backup-09.sql.gz (meaning last daily backup) in some folder there are weekly backups and other are daily backups. please compare last (weekly/daily) with the previous one sorted descending by modification date using the size information of the file. A correct backup will be similar in size, for example 31.41 Mb and 31.33Mb. Make report with directories with status of the backups showing OK or Error based on explained failed.
81
+
82
+ I have verified the two most recent backup files for each directory, comparing their timestamps and file sizes. "Similar size" is interpreted as a difference of less than 5%, which indicates a successful backup chain.
83
+
84
+ #### QNAP Backup Status Report
85
+ **Date:** 2026-02-09
86
+
87
+ | Directory | Frequency | Latest Backup | Previous Backup | Size Comparison | Status |
88
+ | :--- | :--- | :--- | :--- | :--- | :--- |
89
+ | **ayudarg** | Weekly | `alldb_backup-2.sql.gz`<br>(Feb 08, 868.7 KB) | `alldb_backup-1.sql.gz`<br>(Feb 01, 868.7 KB) | Diff: 2 bytes (~0%) | **OK** |
90
+ | **glpi** | Daily | `alldb_backup-09.sql.gz`<br>(Feb 09, 32.9 MB) | `alldb_backup-08.sql.gz`<br>(Feb 08, 32.8 MB) | Diff: ~89 KB (~0.2%) | **OK** |
91
+ | **invop** | Weekly | `alldb_backup-2.sql.gz`<br>(Feb 08, 1.41 MB) | `alldb_backup-1.sql.gz`<br>(Feb 01, 1.41 MB) | Diff: 1 byte (~0%) | **OK** |
92
+ | **kanboard** | Daily | `alldb_backup-09.sql.gz`<br>(Feb 09, 2.97 MB) | `alldb_backup-08.sql.gz`<br>(Feb 08, 2.92 MB) | Diff: ~45 KB (~1.5%) | **OK** |
93
+ | **mon** | Weekly | `alldb_backup-2.sql.gz`<br>(Feb 08, 33.6 MB) | `alldb_backup-1.sql.gz`<br>(Feb 01, 33.7 MB) | Diff: ~117 KB (~0.3%) | **OK** |
94
+ | **moodle** | Daily | `full-backup-09.sql.gz`<br>(Feb 09, 654 MB) | `full-backup-08.sql.gz`<br>(Feb 08, 653 MB) | Diff: ~234 KB (~0.03%) | **OK** |
95
+ | **mygis** | Weekly | `full-backup-2.dump`<br>(Feb 08, 314 KB) | `full-backup-1.dump`<br>(Feb 01, 314 KB) | Diff: 0 bytes (0%) | **OK** |
96
+ | **ocular-vet** | Weekly | `backup-OcularVetDB-2.dump`<br>(Feb 08, 251 KB) | `backup-OcularVetDB-1.dump`<br>(Feb 01, 251 KB) | Diff: ~623 bytes (0.2%) | **OK** |
97
+ | **patogis** | Weekly | `full-backup-2.dump`<br>(Feb 08, 438 KB) | `full-backup-1.dump`<br>(Feb 01, 438 KB) | Diff: 0 bytes (0%) | **OK** |
98
+ | **piexa** | Daily | `full-backup-09.sql.gz`<br>(Feb 09, 162 MB) | `full-backup-08.sql.gz`<br>(Feb 08, 163 MB) | Diff: ~2.2 MB (~0.1%) | **OK** |
99
+ | **web** | Daily | `wordpress_backup-09.sql.gz`<br>(Feb 09, 23.8 MB) | `wordpress_backup-08.sql.gz`<br>(Feb 08, 23.8 MB) | Diff: ~12 KB (~0.05%) | **OK** |
100
+ | **yact** | Weekly | `backup-YactDB-2.gz`<br>(Feb 08, 2.14 MB) | `backup-YactDB-1.gz`<br>(Feb 01, 2.14 MB) | Diff: 0 bytes (0%) | **OK** |
101
+
102
+ #### Summary
103
+ All analyzed directories show consistent backup sizes and up-to-date timestamps (Feb 08 for weekly, Feb 09 for daily). No errors were detected.
104
+
105
+ ## Using Gemini CLI
106
+
107
+ [Gemini CLI](https://github.com/google-gemini/gemini-cli/)
108
+ is an open-source AI agent that brings the power of Gemini directly
109
+ into your terminal. It provides lightweight access to Gemini, giving you the
110
+ most direct path from your prompt to our model.
111
+
112
+ Using this sample settings.json file at ~/.gemini/ directory:
113
+
114
+ ```json
115
+ {
116
+ "mcpServers": {
117
+ "qnap": {
118
+ "command": "npx",
119
+ "args": [
120
+ "-y",
121
+ "@marcelo-ochoa/server-qnap",
122
+ "http://10.1.1.241:8080"
123
+ ],
124
+ "env": {
125
+ "QNAP_USER": "admin",
126
+ "QNAP_PASSWORD": "password"
127
+ }
128
+ }
129
+ },
130
+ "security": {
131
+ "auth": {
132
+ "selectedType": "gemini-api-key"
133
+ }
134
+ },
135
+ "ui": {
136
+ "theme": "ANSI"
137
+ },
138
+ "selectedAuthType": "gemini-api-key",
139
+ "theme": "Dracula"
140
+ }
141
+ ```
142
+
143
+ ### Sample prompts with Gemini CLI
144
+
145
+ - qnap-connect to http://10.1.1.241:8080 using admin as user and password as password using qnap mcp server
146
+ - qnap-report
147
+ - analyze backup files in several subdirectories of `/backup/stacks` on the QNAP NAS. For each subdirectory compare the two most recent backups (daily or weekly, identified by filenames like `alldb_backup-2.sql.gz` for weekly and `alldb_backup-09.sql.gz` for daily). The comparison should be based on file size, considering backups with similar sizes as correct. Generate a report indicating the status (OK or Error) for each directory based on this comparison.
148
+
149
+ ## Using Antigravity Code Editor
150
+
151
+ Put this in `~/.gemini/antigravity/mcp_config.json`
152
+
153
+ ```json
154
+ {
155
+ "mcpServers": {
156
+ "qnap": {
157
+ "command": "docker",
158
+ "args": [
159
+ "run",
160
+ "-i",
161
+ "--rm",
162
+ "-e",
163
+ "QNAP_USER=admin",
164
+ "-e",
165
+ "QNAP_PASSWORD=password",
166
+ "mochoa/mcp-qnap",
167
+ "http://10.1.1.241:8080"
168
+ ]
169
+ }
170
+ }
171
+ }
172
+ ```
package/Dockerfile ADDED
@@ -0,0 +1,29 @@
1
+ FROM node:slim AS builder
2
+
3
+ COPY src/qnap /app
4
+ COPY tsconfig.json /tsconfig.json
5
+
6
+ WORKDIR /app
7
+
8
+ RUN --mount=type=cache,target=/root/.npm npm install
9
+
10
+ RUN npm run build
11
+
12
+ RUN --mount=type=cache,target=/root/.npm-production npm ci --ignore-scripts --omit-dev
13
+
14
+ FROM node:slim AS release
15
+
16
+ # Update and upgrade to fix OS-level vulnerabilities
17
+ RUN apt-get update && apt-get upgrade -y && rm -rf /var/lib/apt/lists/*
18
+
19
+ COPY --from=builder /app/dist /app/dist
20
+ COPY --from=builder /app/package.json /app/package.json
21
+ COPY --from=builder /app/package-lock.json /app/package-lock.json
22
+
23
+ ENV NODE_ENV=production
24
+
25
+ WORKDIR /app
26
+
27
+ RUN npm ci --ignore-scripts --omit-dev
28
+
29
+ ENTRYPOINT ["node", "dist/index.js"]
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # QNAP MCP Server
2
+
3
+ An MCP server implementation for QNAP NAS devices, providing tools to monitor system status, manage files, and generate reports.
4
+
5
+ ## Tools
6
+
7
+ - `qnap-connect`: Connect to a QNAP NAS and obtain a session ID.
8
+ - `host`: The QNAP NAS URL (e.g., `http://10.1.1.241:8080`).
9
+ - `username`: Your admin username.
10
+ - `password`: Your admin password.
11
+ - `qnap-report`: Generate a system report including CPU, memory, disks, and volume status.
12
+ - `qnap-dir`: List the contents of a directory.
13
+ - `path`: The path to list (e.g., `/Public`).
14
+ - `qnap-file-info`: Get detailed information about a specific file.
15
+ - `path`: The directory path.
16
+ - `filename`: The name of the file.
17
+
18
+ ## Configuration
19
+
20
+ ### Environment Variables
21
+
22
+ The server can use environment variables and startup arguments for automatic connection:
23
+
24
+ - **`QNAP_USER`**: QNAP admin username.
25
+ - **`QNAP_PASSWORD`**: QNAP admin password.
26
+
27
+ ### Startup Arguments
28
+
29
+ 1. **`host`**: (Optional) URL of the QNAP NAS (e.g., `http://10.1.1.241:8080`).
30
+
31
+ If the host and environment variables are provided, the server will attempt to connect automatically at startup.
32
+
33
+ ### Usage with Claude Desktop
34
+
35
+ Add the following to your `claude_desktop_config.json`:
36
+
37
+ ```json
38
+ {
39
+ "mcpServers": {
40
+ "qnap": {
41
+ "command": "npx",
42
+ "args": [
43
+ "-y",
44
+ "@marcelo-ochoa/server-qnap",
45
+ "http://10.1.1.241:8080"
46
+ ],
47
+ "env": {
48
+ "QNAP_USER": "admin",
49
+ "QNAP_PASSWORD": "password"
50
+ }
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Development
57
+
58
+ ```bash
59
+ cd src/qnap
60
+ npm install
61
+ npm run build
62
+ ```
63
+
64
+
65
+ ## Demos
66
+
67
+ See [Demos](https://github.com/marcelo-ochoa/servers/blob/main/src/qnap/Demos.md) for usage examples with Claude Desktop, Gemini CLI, and Antigravity Code Editor.
68
+
69
+ ## Docker
70
+
71
+ Building the container:
72
+
73
+ ```bash
74
+ docker build -t mochoa/mcp-qnap -f src/qnap/Dockerfile .
75
+ ```
76
+
77
+ Running the container:
78
+
79
+ ```bash
80
+ docker run -i --rm -e QNAP_USER=admin -e QNAP_PASSWORD=password mochoa/mcp-qnap http://10.1.1.241:8080
81
+ ```
82
+
83
+ ## Change Log
84
+
85
+ See [Change Log](CHANGELOG.md) for the history of changes.
86
+
87
+ ## License
88
+
89
+ MIT
package/REFACTORING.md ADDED
@@ -0,0 +1,139 @@
1
+ # QNAP MCP Server Refactoring Summary
2
+
3
+ ## Overview
4
+ The QNAP MCP server has been refactored to follow the same architectural patterns as the MySQL MCP server, improving code organization, maintainability, and readability.
5
+
6
+ ## Changes Made
7
+
8
+ ### 1. Tool Handlers Separation (`tools/` directory)
9
+ Created individual handler files for each tool, following the single responsibility principle:
10
+
11
+ - **`tools/connect.ts`**: Handles QNAP NAS connection and authentication
12
+ - Manages connection state (host and session ID)
13
+ - Provides helper functions: `getNasHost()`, `getNasSid()`, `setNasConnection()`, `clearNasConnection()`
14
+ - Exports `fetchWithTimeout()` for use by other handlers
15
+ - Implements `connectHandler()` for the `qnap-connect` tool
16
+ - Implements `initializeApi()` for programmatic connection
17
+
18
+ - **`tools/report.ts`**: Generates QNAP system reports
19
+ - Implements `reportHandler()` for the `qnap-report` tool
20
+ - Fetches system information and formats connection details
21
+
22
+ - **`tools/dir.ts`**: Lists directory contents
23
+ - Implements `dirHandler()` for the `qnap-dir` tool
24
+ - Handles directory listing via QNAP file manager API
25
+
26
+ - **`tools/file_info.ts`**: Retrieves file information
27
+ - Implements `fileInfoHandler()` for the `qnap-file-info` tool
28
+ - Fetches detailed file metadata
29
+
30
+ ### 2. Handler Dispatcher Pattern (`handlers.ts`)
31
+ Simplified the handlers file to act as a clean dispatcher:
32
+
33
+ ```typescript
34
+ const toolHandlers: Record<string, (request: CallToolRequest) => Promise<any>> = {
35
+ "qnap-connect": connectHandler,
36
+ "qnap-report": reportHandler,
37
+ "qnap-dir": dirHandler,
38
+ "qnap-file-info": fileInfoHandler,
39
+ };
40
+
41
+ export const callToolHandler = async (request: CallToolRequest) => {
42
+ const handler = toolHandlers[request.params.name];
43
+ if (handler) {
44
+ return handler(request);
45
+ }
46
+ throw new Error(`Unknown tool: ${request.params.name}`);
47
+ };
48
+ ```
49
+
50
+ ### 3. Tool Definitions (`tools.ts`)
51
+ Refactored to use a simple array-based structure matching MySQL pattern:
52
+
53
+ **Before:**
54
+ ```typescript
55
+ export const QNAP_CONNECT_TOOL: Tool = { ... };
56
+ export const QNAP_REPORT_TOOL: Tool = { ... };
57
+ export const TOOLS = [QNAP_CONNECT_TOOL, QNAP_REPORT_TOOL, ...];
58
+ ```
59
+
60
+ **After:**
61
+ ```typescript
62
+ export const tools = [
63
+ { name: "qnap-connect", description: "...", inputSchema: {...} },
64
+ { name: "qnap-report", description: "...", inputSchema: {...} },
65
+ ...
66
+ ];
67
+ ```
68
+
69
+ ### 4. Server Updates (`server.ts`)
70
+ Updated to use the new dispatcher pattern:
71
+
72
+ **Before:**
73
+ ```typescript
74
+ switch (request.params.name) {
75
+ case "qnap-connect":
76
+ return await handleConnect(request.params.arguments);
77
+ case "qnap-report":
78
+ return await handleReport();
79
+ ...
80
+ }
81
+ ```
82
+
83
+ **After:**
84
+ ```typescript
85
+ return await callToolHandler(request);
86
+ ```
87
+
88
+ ## Benefits
89
+
90
+ 1. **Better Code Organization**: Each tool has its own file with focused responsibility
91
+ 2. **Improved Maintainability**: Changes to one tool don't affect others
92
+ 3. **Consistent Patterns**: Follows the same structure as MySQL MCP server
93
+ 4. **Enhanced Readability**: Smaller, focused files are easier to understand
94
+ 5. **Better Error Handling**: Each handler validates its own inputs
95
+ 6. **Easier Testing**: Individual handlers can be tested in isolation
96
+ 7. **Scalability**: Adding new tools is straightforward - just create a new handler file and register it
97
+
98
+ ## File Structure Comparison
99
+
100
+ ### Before:
101
+ ```
102
+ src/qnap/
103
+ ├── handlers.ts (183 lines - all logic inline)
104
+ ├── tools.ts (58 lines - individual constants)
105
+ ├── server.ts (84 lines - switch statement)
106
+ └── tools/
107
+ └── qnap_report_collector.py
108
+ ```
109
+
110
+ ### After:
111
+ ```
112
+ src/qnap/
113
+ ├── handlers.ts (24 lines - clean dispatcher)
114
+ ├── tools.ts (66 lines - array-based)
115
+ ├── server.ts (70 lines - uses dispatcher)
116
+ └── tools/
117
+ ├── connect.ts (connection logic + state management)
118
+ ├── report.ts (report generation)
119
+ ├── dir.ts (directory listing)
120
+ ├── file_info.ts (file information)
121
+ └── qnap_report_collector.py
122
+ ```
123
+
124
+ ## Migration Notes
125
+
126
+ - All existing functionality is preserved
127
+ - No breaking changes to the API
128
+ - Connection state is now managed through helper functions
129
+ - Each handler validates its own inputs and returns consistent error structures
130
+ - Build process remains unchanged (`npm run build`)
131
+
132
+ ## Next Steps
133
+
134
+ Consider these future enhancements:
135
+ 1. Add TypeScript interfaces for QNAP API responses
136
+ 2. Implement unit tests for individual handlers
137
+ 3. Add more comprehensive error messages
138
+ 4. Consider adding retry logic for network operations
139
+ 5. Add logging/debugging capabilities
@@ -0,0 +1,18 @@
1
+ import { connectHandler, initializeApi } from "./tools/connect.js";
2
+ import { reportHandler } from "./tools/report.js";
3
+ import { dirHandler } from "./tools/dir.js";
4
+ import { fileInfoHandler } from "./tools/file_info.js";
5
+ const toolHandlers = {
6
+ "qnap-connect": connectHandler,
7
+ "qnap-report": reportHandler,
8
+ "qnap-dir": dirHandler,
9
+ "qnap-file-info": fileInfoHandler,
10
+ };
11
+ export const callToolHandler = async (request) => {
12
+ const handler = toolHandlers[request.params.name];
13
+ if (handler) {
14
+ return handler(request);
15
+ }
16
+ throw new Error(`Unknown tool: ${request.params.name}`);
17
+ };
18
+ export { initializeApi };
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { QnapServer } from "./server.js";
3
+ const server = new QnapServer();
4
+ server.run().catch((error) => {
5
+ console.error("Fatal error running server:", error);
6
+ process.exit(1);
7
+ });
package/dist/server.js ADDED
@@ -0,0 +1,69 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { z } from "zod";
5
+ import { tools } from "./tools.js";
6
+ import { callToolHandler, initializeApi } from "./handlers.js";
7
+ const prompts = [
8
+ { name: "qnap-connect: Connect to QNAP NAS", description: "connect to QNAP NAS using host, username and password" },
9
+ { name: "qnap-report: System Report", description: "show a comprehensive system report with CPU, memory, and disk status" },
10
+ { name: "qnap-dir: List Directory", description: "list contents of a directory on the QNAP NAS" },
11
+ { name: "qnap-file-info: File Information", description: "get detailed information about a specific file" }
12
+ ];
13
+ const PromptsListRequestSchema = z.object({
14
+ method: z.literal("prompts/list"),
15
+ params: z.object({}),
16
+ });
17
+ export class QnapServer {
18
+ server;
19
+ constructor() {
20
+ this.server = new Server({
21
+ name: "qnap-mcp-server",
22
+ version: "1.0.1",
23
+ }, {
24
+ capabilities: {
25
+ tools: {},
26
+ prompts: {},
27
+ },
28
+ });
29
+ this.setupHandlers();
30
+ this.server.onerror = (error) => console.error("[MCP Error]", error);
31
+ }
32
+ setupHandlers() {
33
+ this.server.setRequestHandler(PromptsListRequestSchema, async () => {
34
+ return {
35
+ prompts,
36
+ };
37
+ });
38
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
39
+ tools: tools,
40
+ }));
41
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
42
+ return await callToolHandler(request);
43
+ });
44
+ }
45
+ async run() {
46
+ const args = process.argv.slice(2);
47
+ const host = args[0];
48
+ if (host) {
49
+ try {
50
+ await initializeApi(host);
51
+ console.error(`Automatically connected to QNAP NAS at ${host}`);
52
+ }
53
+ catch (error) {
54
+ console.error(`Failed to automatically connect to QNAP NAS: ${error.message}`);
55
+ }
56
+ }
57
+ else {
58
+ console.error("Warning: No QNAP host provided as argument. Use qnap-connect tool before using other functionality.");
59
+ }
60
+ const transport = new StdioServerTransport();
61
+ await this.server.connect(transport);
62
+ console.error("QNAP MCP server running on stdio");
63
+ process.stdin.on("close", () => {
64
+ console.error("QNAP MCP Server closed");
65
+ this.server.close();
66
+ process.exit(0);
67
+ });
68
+ }
69
+ }
@@ -0,0 +1,106 @@
1
+ let nas_host = null;
2
+ let nas_sid = null;
3
+ export function getNasHost() {
4
+ return nas_host;
5
+ }
6
+ export function getNasSid() {
7
+ return nas_sid;
8
+ }
9
+ export function setNasConnection(host, sid) {
10
+ nas_host = host;
11
+ nas_sid = sid;
12
+ }
13
+ export function clearNasConnection() {
14
+ nas_host = null;
15
+ nas_sid = null;
16
+ }
17
+ export async function initializeApi(host, username, password) {
18
+ const user = username || process.env.QNAP_USER;
19
+ const pwd = password || process.env.QNAP_PASSWORD;
20
+ if (!user || !pwd) {
21
+ throw new Error("Credentials not provided and QNAP_USER/QNAP_PASSWORD env variables not set.");
22
+ }
23
+ // Directly perform the connection logic
24
+ const b64_pwd = Buffer.from(pwd).toString('base64');
25
+ const url = `${host}/cgi-bin/authLogin.cgi?user=${user}&pwd=${b64_pwd}`;
26
+ try {
27
+ const response = await fetchWithTimeout(url);
28
+ const text = await response.text();
29
+ // Extract SID using regex
30
+ const sidMatch = text.match(/<authSid><!\[CDATA\[([^\]]*)\]\]><\/authSid>/);
31
+ const sid = sidMatch ? sidMatch[1] : null;
32
+ if (sid) {
33
+ setNasConnection(host, sid);
34
+ }
35
+ else {
36
+ throw new Error(`Login failed. Response: ${text}`);
37
+ }
38
+ }
39
+ catch (error) {
40
+ throw new Error(`Error connecting to QNAP: ${error.message}`);
41
+ }
42
+ }
43
+ async function fetchWithTimeout(url, options = {}, timeout = 60000) {
44
+ const controller = new AbortController();
45
+ const id = setTimeout(() => controller.abort(), timeout);
46
+ const headers = {
47
+ ...options.headers,
48
+ 'Referer': nas_host ? `${nas_host}/cgi-bin/index.cgi` : '',
49
+ };
50
+ if (nas_sid) {
51
+ headers['Cookie'] = `NAS_SID=${nas_sid}`;
52
+ }
53
+ try {
54
+ const response = await fetch(url, {
55
+ ...options,
56
+ headers,
57
+ signal: controller.signal
58
+ });
59
+ clearTimeout(id);
60
+ return response;
61
+ }
62
+ catch (error) {
63
+ clearTimeout(id);
64
+ throw error;
65
+ }
66
+ }
67
+ export async function connectHandler(request) {
68
+ const { host, username, password } = request.params.arguments || {};
69
+ if (typeof host !== "string" || !host ||
70
+ typeof username !== "string" || !username ||
71
+ typeof password !== "string" || !password) {
72
+ return {
73
+ content: [{ type: "text", text: "Missing or invalid host, username, or password argument." }],
74
+ isError: true,
75
+ };
76
+ }
77
+ const b64_pwd = Buffer.from(password).toString('base64');
78
+ const url = `${host}/cgi-bin/authLogin.cgi?user=${username}&pwd=${b64_pwd}`;
79
+ try {
80
+ const response = await fetchWithTimeout(url);
81
+ const text = await response.text();
82
+ // Extract SID using regex
83
+ const sidMatch = text.match(/<authSid><!\[CDATA\[([^\]]*)\]\]><\/authSid>/);
84
+ const sid = sidMatch ? sidMatch[1] : null;
85
+ if (sid) {
86
+ setNasConnection(host, sid);
87
+ return {
88
+ content: [{ type: "text", text: `Connected successfully to ${host}. SID: ${sid}` }],
89
+ isError: false,
90
+ };
91
+ }
92
+ else {
93
+ return {
94
+ content: [{ type: "text", text: `Login failed. Response: ${text}` }],
95
+ isError: true
96
+ };
97
+ }
98
+ }
99
+ catch (error) {
100
+ return {
101
+ content: [{ type: "text", text: `Error connecting to QNAP: ${error.message}` }],
102
+ isError: true
103
+ };
104
+ }
105
+ }
106
+ export { fetchWithTimeout };