feat(infra): Setup Synology AI Factory (Gitea + MCP)
This commit is contained in:
parent
94af953ac3
commit
3841cbb907
|
|
@ -25,14 +25,14 @@ services:
|
||||||
- GITEA__database__HOST=db:5432
|
- GITEA__database__HOST=db:5432
|
||||||
- GITEA__database__NAME=gitea
|
- GITEA__database__NAME=gitea
|
||||||
- GITEA__database__USER=gitea
|
- GITEA__database__USER=gitea
|
||||||
- GITEA__database__PASSWD=gitea_password # 【変更推奨】DBパスワード
|
- GITEA__database__PASSWD=P@ssword_gitea_822929! # 【変更推奨】DBパスワード
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
- gitea_network
|
- gitea_network
|
||||||
volumes:
|
volumes:
|
||||||
- ./gitea:/data # リポジトリデータ (永続化)
|
- ./gitea:/data # リポジトリデータ (永続化)
|
||||||
- /etc/timezone:/etc/timezone:ro
|
# - /etc/timezone:/etc/timezone:ro
|
||||||
- /etc/localtime:/etc/localtime:ro
|
# - /etc/localtime:/etc/localtime:ro
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000" # Web UI用ポート (ブラウザからアクセス)
|
- "3000:3000" # Web UI用ポート (ブラウザからアクセス)
|
||||||
- "2222:22" # SSH用ポート (Git操作用)
|
- "2222:22" # SSH用ポート (Git操作用)
|
||||||
|
|
@ -47,7 +47,7 @@ services:
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=gitea
|
- POSTGRES_USER=gitea
|
||||||
- POSTGRES_PASSWORD=gitea_password # 【変更推奨】Giteaの設定と合わせる
|
- POSTGRES_PASSWORD=P@ssword_gitea_822929! # 【変更推奨】Giteaの設定と合わせる
|
||||||
- POSTGRES_DB=gitea
|
- POSTGRES_DB=gitea
|
||||||
networks:
|
networks:
|
||||||
- gitea_network
|
- gitea_network
|
||||||
|
|
@ -69,7 +69,7 @@ services:
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- 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
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
- gitea_network
|
- gitea_network
|
||||||
|
|
|
||||||
|
|
@ -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: {} };
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue