Skip to main content
Glama
server.ts37.7 kB
/** * Stellaris Modding MCP Server * ステラリスのmodding用MCPサーバー * SteamCMD APIを使用してステラリスの最新バージョン情報を取得する */ import { FastMCP } from "fastmcp"; import { z } from "zod"; const server = new FastMCP({ instructions: `ステラリスのmodding支援ツール。以下の機能を提供します: 1. **ゲームバージョン情報** (stellaris-version): SteamCMD APIからステラリスの最新バージョン、ビルドID、更新日時、利用可能なバージョン一覧を取得 2. **CWToolsコンフィグ** (cwtools-config): cwtools/cwtools-stellaris-configリポジトリからStellarisのmodding検証ルール(.cwt files)を取得。安定版/開発版の選択、特定ファイルまたは全ファイル一覧の取得が可能 3. **ドキュメント検索** (stellaris-search): OldEnt/stellaris-triggers-modifiers-effects-listリポジトリから最新バージョンのドキュメントを検索し、指定したキーワードに一致する箇所をコードスニペットとして返す 4. **要素検証** (stellaris-validate): 指定したtrigger、effect、modifier、scope、localizationが最新バージョンで有効かどうかをチェック 全ての機能にリトライ機能とエラーハンドリングが組み込まれており、安定した動作を提供します。`, name: "Stellaris Modding Helper", version: "1.0.0", }); // ステラリスのApp ID const STELLARIS_APP_ID = "281990"; const STEAMCMD_API_BASE = "https://api.steamcmd.net/v1"; // CWTools config repository const CWTOOLS_REPO_OWNER = "cwtools"; const CWTOOLS_REPO_NAME = "cwtools-stellaris-config"; const GITHUB_API_BASE = "https://api.github.com"; // OldEnt Stellaris documentation repository const STELLARIS_DOCS_OWNER = "OldEnt"; const STELLARIS_DOCS_REPO = "stellaris-triggers-modifiers-effects-list"; // リトライ設定 const RETRY_CONFIG = { backoffMultiplier: 2, baseDelay: 1000, // 1秒 maxDelay: 10000, // 10秒 maxRetries: 3, }; /** * SteamCMD APIからアプリ情報を取得する */ async function fetchSteamAppInfo(appId: string): Promise<unknown> { const response = await fetchWithRetry(`${STEAMCMD_API_BASE}/info/${appId}`); if (!response.ok) { throw new Error( `Failed to fetch app info: ${response.status} ${response.statusText}`, ); } const data = (await response.json()) as { data: Record<string, unknown>; status: string; }; if (data.status !== "success") { throw new Error(`API returned error: ${JSON.stringify(data.data)}`); } return data.data[appId]; } /** * 指数バックオフを使用したリトライ機能付きfetch */ async function fetchWithRetry( url: string, options?: RequestInit, retryCount = 0, ): Promise<Response> { try { const response = await fetch(url, options); // レート制限の場合は特別な処理 if (response.status === 403 && response.statusText.includes("rate limit")) { if (retryCount < RETRY_CONFIG.maxRetries) { const delay = Math.min( RETRY_CONFIG.baseDelay * Math.pow(RETRY_CONFIG.backoffMultiplier, retryCount), RETRY_CONFIG.maxDelay, ); console.log( `Rate limit hit, retrying in ${delay}ms (attempt ${retryCount + 1}/${RETRY_CONFIG.maxRetries})`, ); await new Promise((resolve) => setTimeout(resolve, delay)); return fetchWithRetry(url, options, retryCount + 1); } } // 一時的なサーバーエラーの場合もリトライ if (response.status >= 500 && response.status < 600) { if (retryCount < RETRY_CONFIG.maxRetries) { const delay = Math.min( RETRY_CONFIG.baseDelay * Math.pow(RETRY_CONFIG.backoffMultiplier, retryCount), RETRY_CONFIG.maxDelay, ); console.log( `Server error ${response.status}, retrying in ${delay}ms (attempt ${retryCount + 1}/${RETRY_CONFIG.maxRetries})`, ); await new Promise((resolve) => setTimeout(resolve, delay)); return fetchWithRetry(url, options, retryCount + 1); } } return response; } catch (error) { // ネットワークエラーの場合もリトライ if (retryCount < RETRY_CONFIG.maxRetries) { const delay = Math.min( RETRY_CONFIG.baseDelay * Math.pow(RETRY_CONFIG.backoffMultiplier, retryCount), RETRY_CONFIG.maxDelay, ); console.log( `Network error, retrying in ${delay}ms (attempt ${retryCount + 1}/${RETRY_CONFIG.maxRetries}):`, error instanceof Error ? error.message : String(error), ); await new Promise((resolve) => setTimeout(resolve, delay)); return fetchWithRetry(url, options, retryCount + 1); } throw error; } } /** * Unix timestampを読みやすい日付形式に変換 */ function formatTimestamp(timestamp: number | string): string { const date = new Date(Number(timestamp) * 1000); return date.toLocaleString("ja-JP", { day: "2-digit", hour: "2-digit", minute: "2-digit", month: "2-digit", second: "2-digit", timeZone: "Asia/Tokyo", year: "numeric", }); } /** * リポジトリのファイル一覧を取得し、利用可能なバージョン一覧を取得する(降順ソート済み) */ async function getAvailableStellarisVersions( owner: string, repo: string, ): Promise<string[]> { const response = await fetchWithRetry( `${GITHUB_API_BASE}/repos/${owner}/${repo}/contents`, ); if (!response.ok) { throw new Error( `Failed to fetch repository contents: ${response.status} ${response.statusText}`, ); } const contents = (await response.json()) as Array<{ name: string; type: string; }>; // バージョン番号らしきファイルを抽出(数字.数字.数字の形式) const versionFiles = contents .filter((item) => item.type === "file") .map((item) => item.name) .filter((name) => /^\d+\.\d+\.\d+_game_/.test(name)) .map((name) => { const match = name.match(/^(\d+\.\d+\.\d+)_game_/); return match ? match[1] : null; }) .filter((version): version is string => version !== null); // 重複を除去 const uniqueVersions = [...new Set(versionFiles)]; // バージョン番号でソート(降順) uniqueVersions.sort((a, b) => { const aVersion = a.split(".").map((n) => parseInt(n) || 0); const bVersion = b.split(".").map((n) => parseInt(n) || 0); for (let i = 0; i < Math.max(aVersion.length, bVersion.length); i++) { const diff = (bVersion[i] || 0) - (aVersion[i] || 0); if (diff !== 0) return diff; } return 0; }); return uniqueVersions; } /** * GitHub APIからディレクトリの内容を取得する */ async function getDirectoryContents( owner: string, repo: string, path: string, ref?: string, ): Promise< Array<{ download_url: string; name: string; size: number; type: string; }> > { const url = new URL( `${GITHUB_API_BASE}/repos/${owner}/${repo}/contents/${path}`, ); if (ref) { url.searchParams.set("ref", ref); } const response = await fetchWithRetry(url.toString()); if (!response.ok) { throw new Error( `Failed to fetch directory contents: ${response.status} ${response.statusText}`, ); } const contents = (await response.json()) as Array<{ download_url: string; name: string; size: number; type: string; }>; if (!Array.isArray(contents)) { throw new Error("Expected directory contents to be an array"); } return contents; } /** * GitHub APIからファイルの内容を取得する */ async function getFileContent( owner: string, repo: string, path: string, ref?: string, ): Promise<string> { const url = new URL( `${GITHUB_API_BASE}/repos/${owner}/${repo}/contents/${path}`, ); if (ref) { url.searchParams.set("ref", ref); } const response = await fetchWithRetry(url.toString()); if (!response.ok) { throw new Error( `Failed to fetch file content: ${response.status} ${response.statusText}`, ); } const fileData = (await response.json()) as { content: string; encoding: string; }; if (fileData.encoding === "base64") { return Buffer.from(fileData.content, "base64").toString("utf-8"); } return fileData.content; } /** * GitHub APIから最新のタグを取得する */ async function getLatestTag(owner: string, repo: string): Promise<string> { const response = await fetchWithRetry( `${GITHUB_API_BASE}/repos/${owner}/${repo}/tags`, ); if (!response.ok) { throw new Error( `Failed to fetch tags: ${response.status} ${response.statusText}`, ); } const tags = (await response.json()) as Array<{ name: string }>; if (tags.length === 0) { throw new Error("No tags found in repository"); } return tags[0].name; } // ステラリスの現在のバージョン情報を取得するツール server.addTool({ annotations: { openWorldHint: true, // 外部APIにアクセスする readOnlyHint: true, // データを変更しない title: "Stellaris Version Info", }, description: "ステラリスの現在のバージョン情報を取得します。ビルドID、最終更新日時、利用可能なバージョン番号などmodding に必要な情報を提供します。", execute: async (_args, { log }) => { try { log.info("Fetching Stellaris version info from SteamCMD API..."); const appInfo = (await fetchSteamAppInfo(STELLARIS_APP_ID)) as { common?: { name?: string; oslist?: string }; config?: { installdir?: string }; depots?: { branches?: Record< string, { buildid?: string; description?: string; timeupdated?: string } >; }; extended?: { developer?: string; publisher?: string }; }; const branches = appInfo.depots?.branches; if (!branches) { throw new Error("Branch information not found in API response"); } const publicBranch = branches.public; // バージョン番号らしきブランチを抽出(数字.数字.数字の形式) const versionBranches = Object.keys(branches) .filter((branch) => /^\d+\.\d+/.test(branch)) .sort((a, b) => { // バージョン番号でソート const aVersion = a.split(".").map((n) => parseInt(n) || 0); const bVersion = b.split(".").map((n) => parseInt(n) || 0); for (let i = 0; i < Math.max(aVersion.length, bVersion.length); i++) { const diff = (bVersion[i] || 0) - (aVersion[i] || 0); if (diff !== 0) return diff; } return 0; }); const latestVersionBranch = versionBranches[0]; const currentVersionInfo = latestVersionBranch ? branches[latestVersionBranch] : null; const result = { availableVersions: versionBranches.slice(0, 10), // 最新10バージョンを表示 currentPublic: { buildId: publicBranch?.buildid, lastUpdated: publicBranch?.timeupdated ? formatTimestamp(publicBranch.timeupdated) : "Unknown", lastUpdatedUnix: publicBranch?.timeupdated, }, gameInfo: { appId: STELLARIS_APP_ID, developer: appInfo.extended?.developer, installDir: appInfo.config?.installdir, publisher: appInfo.extended?.publisher, supportedOS: appInfo.common?.oslist, }, gameName: appInfo.common?.name || "Stellaris", latestVersion: latestVersionBranch ? { buildId: currentVersionInfo?.buildid, description: currentVersionInfo?.description || "説明なし", lastUpdated: currentVersionInfo?.timeupdated ? formatTimestamp(currentVersionInfo.timeupdated) : "Unknown", lastUpdatedUnix: currentVersionInfo?.timeupdated, version: latestVersionBranch, } : null, specialBranches: Object.keys(branches).filter( (branch) => !versionBranches.includes(branch) && branch !== "public", ), }; log.info("Successfully retrieved Stellaris version info", { latestVersion: result.latestVersion?.version, publicBuildId: result.currentPublic.buildId, }); return { content: [ { text: `# ステラリス バージョン情報 ## 現在のPublicブランチ - **ビルドID**: ${result.currentPublic.buildId} - **最終更新**: ${result.currentPublic.lastUpdated} ${result.latestVersion ? `## 最新バージョン - **バージョン**: ${result.latestVersion.version} - **ビルドID**: ${result.latestVersion.buildId} - **説明**: ${result.latestVersion.description} - **最終更新**: ${result.latestVersion.lastUpdated}` : "" } ## ゲーム情報 - **ゲーム名**: ${result.gameName} - **App ID**: ${result.gameInfo.appId} - **インストールディレクトリ**: ${result.gameInfo.installDir} - **対応OS**: ${result.gameInfo.supportedOS} - **開発者**: ${result.gameInfo.developer} - **パブリッシャー**: ${result.gameInfo.publisher} ## 利用可能なバージョン(最新10件) ${result.availableVersions.map((version) => `- ${version}`).join("\n")} ## 特別なブランチ ${result.specialBranches.map((branch) => `- ${branch}`).join("\n")} --- *データ取得元: SteamCMD API (https://api.steamcmd.net/)*`, type: "text", }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log.error("Failed to fetch Stellaris version info", { error: errorMessage, }); throw new Error( `ステラリスのバージョン情報の取得に失敗しました: ${errorMessage}`, ); } }, name: "stellaris-version", parameters: z.object({}), // パラメータなし }); // CWToolsのStellarisコンフィグルールを取得するツール server.addTool({ annotations: { openWorldHint: true, // GitHub APIにアクセスする readOnlyHint: true, // データを変更しない title: "CWTools Stellaris Config", }, description: "CWToolsのStellarisコンフィグルール(.cwt files)を取得します。最新の安定版または開発版から、config/直下およびサブディレクトリ(common/等)内の特定のファイルまたは全ファイルを取得できます。", execute: async (args, { log }) => { try { log.info("Fetching CWTools Stellaris config...", { file: args.file, listOnly: args.listOnly, version: args.version, }); // 参照するブランチ/タグを決定 let ref: string | undefined; if (args.version === "stable") { ref = await getLatestTag(CWTOOLS_REPO_OWNER, CWTOOLS_REPO_NAME); log.info(`Using stable version: ${ref}`); } else { log.info("Using latest development version (main branch)"); } // configディレクトリの内容を取得 const configContents = await getDirectoryContents( CWTOOLS_REPO_OWNER, CWTOOLS_REPO_NAME, "config", ref, ); // .cwtファイルのみをフィルタ(ルートレベル) const rootCwtFiles = configContents.filter( (item) => item.type === "file" && item.name.endsWith(".cwt"), ); // サブディレクトリからも.cwtファイルを取得 const subdirectories = configContents.filter( (item) => item.type === "dir", ); const nestedCwtFiles: Array<{ download_url: string; name: string; path: string; size: number; }> = []; for (const subdir of subdirectories) { let retryCount = 0; const maxSubdirRetries = 2; while (retryCount <= maxSubdirRetries) { try { log.debug( `Reading subdirectory: ${subdir.name} (attempt ${retryCount + 1})`, ); const subdirContents = await getDirectoryContents( CWTOOLS_REPO_OWNER, CWTOOLS_REPO_NAME, `config/${subdir.name}`, ref, ); const subdirCwtFiles = subdirContents .filter( (item) => item.type === "file" && item.name.endsWith(".cwt"), ) .map((file) => ({ download_url: file.download_url, name: file.name, path: `${subdir.name}/${file.name}`, size: file.size, })); nestedCwtFiles.push(...subdirCwtFiles); log.info( `Successfully read ${subdirCwtFiles.length} .cwt files from ${subdir.name}/`, ); // 成功したらループを抜ける break; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (retryCount < maxSubdirRetries) { const delay = 1000 * (retryCount + 1); // 1秒、2秒、3秒 log.warn( `Failed to read subdirectory ${subdir.name} (attempt ${retryCount + 1}), retrying in ${delay}ms`, { error: errorMessage, }, ); await new Promise((resolve) => setTimeout(resolve, delay)); retryCount++; } else { log.error( `Failed to read subdirectory ${subdir.name} after ${maxSubdirRetries + 1} attempts`, { error: errorMessage, }, ); break; } } } // API制限を考慮して少し待機 await new Promise((resolve) => setTimeout(resolve, 200)); } // 全ての.cwtファイルを統合 const allCwtFiles = [ ...rootCwtFiles.map((file) => ({ download_url: file.download_url, name: file.name, path: file.name, size: file.size, })), ...nestedCwtFiles, ]; // 後続の処理で使用するため、cwtFilesをallCwtFilesに置き換え const cwtFiles = allCwtFiles; if (args.listOnly) { // ファイル一覧のみを返す const fileList = cwtFiles.map((file) => ({ downloadUrl: file.download_url, name: file.name, path: file.path, size: file.size, })); return { content: [ { text: `# CWTools Stellaris Config Files (${args.version}) ${ref ? `**Version**: ${ref}` : "**Version**: Latest development"} **Repository**: ${CWTOOLS_REPO_OWNER}/${CWTOOLS_REPO_NAME} **Total .cwt files**: ${fileList.length} ## Available Config Files ${fileList.map((file) => `- **${file.path || file.name}** (${file.size} bytes)`).join("\n")} --- *Use the tool again with a specific file parameter to fetch the content of any file.*`, type: "text", }, ], }; } if (args.file) { // 特定のファイルを取得(ファイル名またはパスで検索) const targetFile = cwtFiles.find( (file) => file.name === args.file || file.path === args.file || `${file.path}` === args.file, ); if (!targetFile) { throw new Error( `File "${args.file}" not found. Available files: ${cwtFiles.map((f) => f.path || f.name).join(", ")}`, ); } const fileContent = await getFileContent( CWTOOLS_REPO_OWNER, CWTOOLS_REPO_NAME, `config/${targetFile.path}`, ref, ); return { content: [ { text: `# ${targetFile.path || args.file} ${ref ? `**Version**: ${ref}` : "**Version**: Latest development"} **Repository**: ${CWTOOLS_REPO_OWNER}/${CWTOOLS_REPO_NAME} **File Size**: ${targetFile.size} bytes ## Content \`\`\`cwt ${fileContent} \`\`\` --- *This is a CWTools config file (.cwt) that defines validation rules for Stellaris modding.*`, type: "text", }, ], }; } // 全ファイルを取得(最大10ファイルまで) const filesToFetch = cwtFiles.slice(0, 10); const fileContents: Array<{ content: string; name: string; size: number; }> = []; for (const file of filesToFetch) { let retryCount = 0; const maxFileRetries = 2; while (retryCount <= maxFileRetries) { try { log.debug( `Fetching file: ${file.path || file.name} (attempt ${retryCount + 1})`, ); const content = await getFileContent( CWTOOLS_REPO_OWNER, CWTOOLS_REPO_NAME, `config/${file.path}`, ref, ); fileContents.push({ content, name: file.path || file.name, size: file.size, }); log.debug( `Successfully fetched ${file.path || file.name} (${content.length} characters)`, ); break; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (retryCount < maxFileRetries) { const delay = 1000 * (retryCount + 1); log.warn( `Failed to fetch ${file.path || file.name} (attempt ${retryCount + 1}), retrying in ${delay}ms`, { error: errorMessage, }, ); await new Promise((resolve) => setTimeout(resolve, delay)); retryCount++; } else { log.error( `Failed to fetch ${file.path || file.name} after ${maxFileRetries + 1} attempts`, { error: errorMessage, }, ); break; } } } // API制限を考慮して少し待機 await new Promise((resolve) => setTimeout(resolve, 150)); } const totalFiles = cwtFiles.length; const fetchedFiles = fileContents.length; return { content: [ { text: `# CWTools Stellaris Config Files (${args.version}) ${ref ? `**Version**: ${ref}` : "**Version**: Latest development"} **Repository**: ${CWTOOLS_REPO_OWNER}/${CWTOOLS_REPO_NAME} **Total files**: ${totalFiles} (showing first ${fetchedFiles}) ${fileContents .map( (file) => `## ${file.name} (${file.size} bytes) \`\`\`cwt ${file.content} \`\`\` ---`, ) .join("\n\n")} ${totalFiles > 10 ? `\n*Note: Only showing first 10 files. Use the 'file' parameter to fetch specific files, or 'listOnly' to see all available files.*` : ""} --- *These are CWTools config files (.cwt) that define validation rules for Stellaris modding.*`, type: "text", }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log.error("Failed to fetch CWTools config", { error: errorMessage }); throw new Error(`CWToolsコンフィグの取得に失敗しました: ${errorMessage}`); } }, name: "cwtools-config", parameters: z.object({ file: z .string() .optional() .describe( "取得する特定の.cwtファイル名またはパス (例: common.cwt, common/agreements.cwt)", ), listOnly: z .boolean() .default(false) .describe("ファイル一覧のみを表示する場合はtrue"), version: z .enum(["stable", "latest"]) .default("stable") .describe("取得するバージョン: stable (最新タグ) または latest (開発版)"), }), }); // Stellarisドキュメントからキーワード検索するツール server.addTool({ annotations: { openWorldHint: true, // GitHub APIにアクセスする readOnlyHint: true, // データを変更しない title: "Stellaris Documentation Search", }, description: "OldEnt/stellaris-triggers-modifiers-effects-listリポジトリからStellarisの最新バージョンのドキュメントを検索し、指定したキーワードに一致する箇所をコードスニペットとして返します。大きなファイルを効率的に検索できます。", execute: async (args, { log }) => { try { log.info("Searching Stellaris documentation...", { docType: args.docType, keyword: args.keyword, }); // 利用可能なバージョン一覧を取得 const availableVersions = await getAvailableStellarisVersions( STELLARIS_DOCS_OWNER, STELLARIS_DOCS_REPO, ); if (availableVersions.length === 0) { throw new Error( "No Stellaris documentation versions found in repository", ); } // 最新バージョンから順番に試行 let targetVersion: null | string = null; let fileContent: null | string = null; let fileName: null | string = null; const attemptedVersions: string[] = []; for (const version of availableVersions) { try { fileName = `${version}_game_${args.docType}.log`; log.info(`Attempting to fetch: ${fileName}`); fileContent = await getFileContent( STELLARIS_DOCS_OWNER, STELLARIS_DOCS_REPO, fileName, ); targetVersion = version; log.info(`Successfully found documentation for version: ${version}`); break; } catch { attemptedVersions.push(version); log.warn( `Version ${version} does not have ${args.docType} documentation, trying next version...`, ); continue; } } if (!targetVersion || !fileContent || !fileName) { throw new Error( `No ${args.docType} documentation found for any available version. Attempted versions: ${attemptedVersions.join(", ")}`, ); } // キーワード検索を実行 const lines = fileContent.split("\n"); const searchResults: Array<{ lineNumber: number; content: string; context: string[]; }> = []; const keyword = args.keyword.toLowerCase(); const contextLines = args.contextLines || 2; for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.toLowerCase().includes(keyword)) { // 前後のコンテキストを取得 const startIndex = Math.max(0, i - contextLines); const endIndex = Math.min(lines.length - 1, i + contextLines); const context = lines.slice(startIndex, endIndex + 1); searchResults.push({ lineNumber: i + 1, content: line.trim(), context, }); } } const versionLabel = targetVersion === availableVersions[0] ? `${targetVersion} (Latest)` : `${targetVersion} (Fallback - Latest available for ${args.docType})`; if (searchResults.length === 0) { return { content: [ { text: `# Stellaris ${args.docType.toUpperCase()} Search Results **Version**: ${versionLabel} **Repository**: ${STELLARIS_DOCS_OWNER}/${STELLARIS_DOCS_REPO} **File**: ${fileName} **Keyword**: "${args.keyword}" **Results**: No matches found キーワード "${args.keyword}" に一致する項目が見つかりませんでした。 **検索のヒント:** - 大文字小文字は区別されません - 部分一致で検索されます - 英語のキーワードを使用してください --- *This documentation is automatically extracted from the Stellaris game engine and compiled by OldEnt.*`, type: "text", }, ], }; } // 結果を制限(最大20件) const limitedResults = searchResults.slice(0, 20); const totalResults = searchResults.length; return { content: [ { text: `# Stellaris ${args.docType.toUpperCase()} Search Results **Version**: ${versionLabel} **Repository**: ${STELLARIS_DOCS_OWNER}/${STELLARIS_DOCS_REPO} **File**: ${fileName} **Keyword**: "${args.keyword}" **Results**: ${totalResults} matches found${totalResults > 20 ? " (showing first 20)" : ""} ## Search Results ${limitedResults .map( (result, index) => `### Result ${index + 1} (Line ${result.lineNumber}) **Matching Line**: \`${result.content}\` **Context**: \`\`\` ${result.context.map((line, i) => { const lineNum = result.lineNumber - contextLines + i; const isMatch = i === contextLines; return `${lineNum.toString().padStart(4, " ")}${isMatch ? ">" : ":"} ${line}`; }).join("\n")} \`\`\` ---`, ) .join("\n\n")} ${totalResults > 20 ? `\n*Note: Showing first 20 results out of ${totalResults} total matches. Refine your search keyword for more specific results.*` : ""} --- *This documentation is automatically extracted from the Stellaris game engine and compiled by OldEnt with the help of McAwesome, Erdnuss and the rest of the Stellaris Modding Den Discord crew.*`, type: "text", }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log.error("Failed to search Stellaris documentation", { error: errorMessage, }); throw new Error( `Stellarisドキュメントの検索に失敗しました: ${errorMessage}`, ); } }, name: "stellaris-search", parameters: z.object({ docType: z .enum(["triggers", "effects", "modifiers", "scopes", "localizations"]) .describe( "検索するドキュメントの種類: triggers (条件), effects (効果), modifiers (修正値), scopes (スコープ), localizations (ローカライゼーション)", ), keyword: z .string() .min(1) .describe("検索するキーワード(英語推奨、大文字小文字区別なし)"), contextLines: z .number() .min(0) .max(10) .default(2) .describe("一致した行の前後に表示するコンテキスト行数(デフォルト: 2)"), }), }); // Stellarisの要素が最新バージョンで有効かどうかをチェックするツール server.addTool({ annotations: { openWorldHint: true, // GitHub APIにアクセスする readOnlyHint: true, // データを変更しない title: "Stellaris Element Validator", }, description: "指定したtrigger、effect、modifier、scope、localizationが最新バージョンのStellarisで有効かどうかをチェックします。modding時の互換性確認に便利です。", execute: async (args, { log }) => { try { log.info("Validating Stellaris element...", { docType: args.docType, element: args.element, }); // 利用可能なバージョン一覧を取得 const availableVersions = await getAvailableStellarisVersions( STELLARIS_DOCS_OWNER, STELLARIS_DOCS_REPO, ); if (availableVersions.length === 0) { throw new Error( "No Stellaris documentation versions found in repository", ); } // 最新バージョンから順番に試行 let targetVersion: null | string = null; let fileContent: null | string = null; let fileName: null | string = null; const attemptedVersions: string[] = []; for (const version of availableVersions) { try { fileName = `${version}_game_${args.docType}.log`; log.info(`Attempting to fetch: ${fileName}`); fileContent = await getFileContent( STELLARIS_DOCS_OWNER, STELLARIS_DOCS_REPO, fileName, ); targetVersion = version; log.info(`Successfully found documentation for version: ${version}`); break; } catch { attemptedVersions.push(version); log.warn( `Version ${version} does not have ${args.docType} documentation, trying next version...`, ); continue; } } if (!targetVersion || !fileContent || !fileName) { throw new Error( `No ${args.docType} documentation found for any available version. Attempted versions: ${attemptedVersions.join(", ")}`, ); } // 要素の存在をチェック const lines = fileContent.split("\n"); const element = args.element.toLowerCase(); const exactMatches: Array<{ lineNumber: number; content: string; }> = []; const partialMatches: Array<{ lineNumber: number; content: string; }> = []; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const lineLower = line.toLowerCase(); // 完全一致(単語境界を考慮) const exactMatch = new RegExp(`\\b${element}\\b`).test(lineLower); if (exactMatch) { exactMatches.push({ lineNumber: i + 1, content: line, }); } // 部分一致 else if (lineLower.includes(element)) { partialMatches.push({ lineNumber: i + 1, content: line, }); } } const versionLabel = targetVersion === availableVersions[0] ? `${targetVersion} (Latest)` : `${targetVersion} (Fallback - Latest available for ${args.docType})`; const isValid = exactMatches.length > 0; const hasPartialMatches = partialMatches.length > 0; return { content: [ { text: `# Stellaris ${args.docType.toUpperCase()} Validation **Version**: ${versionLabel} **Repository**: ${STELLARIS_DOCS_OWNER}/${STELLARIS_DOCS_REPO} **File**: ${fileName} **Element**: "${args.element}" **Status**: ${isValid ? "✅ **VALID**" : hasPartialMatches ? "⚠️ **PARTIAL MATCH**" : "❌ **NOT FOUND**"} ## Validation Results ${ isValid ? `### ✅ Exact Matches Found (${exactMatches.length}) The element "${args.element}" is **valid** in the latest Stellaris version. **Occurrences:** ${exactMatches .slice(0, 10) .map( (match) => `- Line ${match.lineNumber}: \`${match.content}\``, ) .join("\n")} ${exactMatches.length > 10 ? `\n*... and ${exactMatches.length - 10} more occurrences*` : ""}` : hasPartialMatches ? `### ⚠️ Partial Matches Found (${partialMatches.length}) The element "${args.element}" was not found as an exact match, but similar elements were found: **Similar Elements:** ${partialMatches .slice(0, 10) .map( (match) => `- Line ${match.lineNumber}: \`${match.content}\``, ) .join("\n")} ${partialMatches.length > 10 ? `\n*... and ${partialMatches.length - 10} more similar elements*` : ""} **Suggestion**: Check if you meant one of the similar elements above, or verify the exact spelling.` : `### ❌ Element Not Found The element "${args.element}" was not found in the ${args.docType} documentation. **Possible reasons:** - The element name is misspelled - The element has been removed or renamed in recent versions - The element belongs to a different category (try searching in other docTypes) **Suggestion**: Try using the \`stellaris-search\` tool to search for similar elements.` } ## Usage Information ${ isValid ? `This ${args.docType.slice(0, -1)} can be safely used in your Stellaris mods for version ${targetVersion} and should be compatible with the current game version.` : hasPartialMatches ? `While "${args.element}" wasn't found exactly, there are similar elements available. Please verify the correct spelling or consider using one of the similar elements shown above.` : `This element is not available in the current Stellaris version. Consider using alternative elements or check if the functionality has been moved to a different category.` } --- *This validation is based on documentation automatically extracted from the Stellaris game engine and compiled by OldEnt.*`, type: "text", }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log.error("Failed to validate Stellaris element", { error: errorMessage, }); throw new Error( `Stellaris要素の検証に失敗しました: ${errorMessage}`, ); } }, name: "stellaris-validate", parameters: z.object({ docType: z .enum(["triggers", "effects", "modifiers", "scopes", "localizations"]) .describe( "検証するドキュメントの種類: triggers (条件), effects (効果), modifiers (修正値), scopes (スコープ), localizations (ローカライゼーション)", ), element: z .string() .min(1) .describe("検証する要素名(例: has_technology, add_modifier, naval_capacity)"), }), }); server.start({ transportType: "stdio", });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kongyo2/Stellaris-Modding-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server