From 3841cbb907d90be90426ab1ea264c6d6a619b44d Mon Sep 17 00:00:00 2001 From: Ponshu Developer Date: Wed, 14 Jan 2026 00:48:04 +0900 Subject: [PATCH] feat(infra): Setup Synology AI Factory (Gitea + MCP) --- tools/synology/docker-compose.yml | 10 +- tools/synology/mcp/index.js | 184 ++++++++++++++++++++++++++++++ tools/synology/mcp/package.json | 14 +++ 3 files changed, 203 insertions(+), 5 deletions(-) create mode 100644 tools/synology/mcp/index.js create mode 100644 tools/synology/mcp/package.json diff --git a/tools/synology/docker-compose.yml b/tools/synology/docker-compose.yml index 997d43d..11b09a8 100644 --- a/tools/synology/docker-compose.yml +++ b/tools/synology/docker-compose.yml @@ -25,14 +25,14 @@ services: - GITEA__database__HOST=db:5432 - GITEA__database__NAME=gitea - GITEA__database__USER=gitea - - GITEA__database__PASSWD=gitea_password # 【変更推奨】DBパスワード + - GITEA__database__PASSWD=P@ssword_gitea_822929! # 【変更推奨】DBパスワード restart: always networks: - gitea_network volumes: - ./gitea:/data # リポジトリデータ (永続化) - - /etc/timezone:/etc/timezone:ro - - /etc/localtime:/etc/localtime:ro + # - /etc/timezone:/etc/timezone:ro + # - /etc/localtime:/etc/localtime:ro ports: - "3000:3000" # Web UI用ポート (ブラウザからアクセス) - "2222:22" # SSH用ポート (Git操作用) @@ -47,7 +47,7 @@ services: restart: always environment: - POSTGRES_USER=gitea - - POSTGRES_PASSWORD=gitea_password # 【変更推奨】Giteaの設定と合わせる + - POSTGRES_PASSWORD=P@ssword_gitea_822929! # 【変更推奨】Giteaの設定と合わせる - POSTGRES_DB=gitea networks: - gitea_network @@ -69,7 +69,7 @@ services: environment: - NODE_ENV=development # 初回起動時に必要なライブラリを自動インストールして待機 - command: sh -c "npm init -y && npm install @modelcontextprotocol/sdk && node index.js || tail -f /dev/null" + command: sh -c "npm install && node index.js; tail -f /dev/null" restart: always networks: - gitea_network diff --git a/tools/synology/mcp/index.js b/tools/synology/mcp/index.js new file mode 100644 index 0000000..4ac8a94 --- /dev/null +++ b/tools/synology/mcp/index.js @@ -0,0 +1,184 @@ +#!/usr/bin/env node + +/** + * Posimai Lab MCP Server + * + * Provides File System and Gitea integration for the AI Agent. + * Communication: Stdio (Standard Input/Output) + * Scope: + * - /app (Server code) + * - /data/gitea_files (Gitea Repo Data) + */ + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { z } from "zod"; +import fs from "fs/promises"; +import path from "path"; + +// Define allowed scopes for security +const ALLOWED_Root = "/data/gitea_files"; +const APP_Root = "/app"; + +// Helper to validate paths +function validatePath(requestedPath) { + const resolved = path.resolve(requestedPath); + if (resolved.startsWith(ALLOWED_Root) || resolved.startsWith(APP_Root)) { + return resolved; + } + throw new Error(`Access denied: Path ${resolved} is outside allowed scopes.`); +} + +// Create Server Instance +const server = new Server( + { + name: "posimai-mcp-server", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + }, + } +); + +/** + * Tool Definitions + */ +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "list_files", + description: "List files in a directory within the allowed scope.", + inputSchema: zodToJsonSchema( + z.object({ + path: z.string().describe("Directory path to list (e.g., /data/gitea_files)"), + }) + ), + }, + { + name: "read_file", + description: "Read the content of a text file.", + inputSchema: zodToJsonSchema( + z.object({ + path: z.string().describe("File path to read"), + }) + ), + }, + { + name: "write_file", + description: "Write content to a file (overwrite).", + inputSchema: zodToJsonSchema( + z.object({ + path: z.string().describe("File path to write"), + content: z.string().describe("Content to write"), + }) + ), + }, + { + name: "hello_world", + description: "Simple test tool to verify connection.", + inputSchema: zodToJsonSchema(z.object({})), + }, + ], + }; +}); + +/** + * Tool Execution Handler + */ +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + switch (name) { + case "hello_world": + return { + content: [ + { + type: "text", + text: "Hello from Synology! MCP Server is running successfully. 🚀", + }, + ], + }; + + case "list_files": { + const dirPath = validatePath(String(args.path)); + const files = await fs.readdir(dirPath, { withFileTypes: true }); + const listing = files.map((f) => + `${f.isDirectory() ? "[DIR]" : "[FILE]"} ${f.name}` + ).join("\n"); + return { + content: [{ type: "text", text: listing || "(Empty Directory)" }], + }; + } + + case "read_file": { + const filePath = validatePath(String(args.path)); + const content = await fs.readFile(filePath, "utf-8"); + return { + content: [{ type: "text", text: content }], + }; + } + + case "write_file": { + const filePath = validatePath(String(args.path)); + await fs.writeFile(filePath, String(args.content), "utf-8"); + return { + content: [{ type: "text", text: `Successfully wrote to ${filePath}` }], + }; + } + + default: + throw new Error(`Unknown tool: ${name}`); + } + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error: ${error instanceof Error ? error.message : String(error)}`, + }, + ], + isError: true, + }; + } +}); + +// Start Server +async function run() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("MCP Server running on stdio"); +} + +run().catch((error) => { + console.error("Fatal error running server:", error); + process.exit(1); +}); + + +// --- Helper for Zod to JSON Schema (Simplified) --- +function zodToJsonSchema(zodObj) { + // In a real app we'd use 'zod-to-json-schema' package, + // but for simplicity/dependencies we'll do basic mapping here or use the loose definition. + // Actually, let's keep it simple for now as the strict schema isn't strictly enforced by the starter client yet, + // but better to specific properties. + + // Quick-and-dirty conversion for the simple objects we use above + if (zodObj._def.typeName === 'ZodObject') { + const properties = {}; + const required = []; + for (const [key, schema] of Object.entries(zodObj.shape)) { + properties[key] = { type: 'string', description: schema.description }; + if (!schema.isOptional()) required.push(key); + } + return { type: 'object', properties, required }; + } + return { type: 'object', properties: {} }; +} diff --git a/tools/synology/mcp/package.json b/tools/synology/mcp/package.json new file mode 100644 index 0000000..7f595b3 --- /dev/null +++ b/tools/synology/mcp/package.json @@ -0,0 +1,14 @@ +{ + "name": "posimai-mcp-server", + "version": "1.0.0", + "description": "MCP Server for Ponshu Room Synology Environment", + "type": "module", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^0.6.0", + "zod": "^3.23.8" + } +} \ No newline at end of file