Skip to main content
Glama
kongyo2

Stellaris Modding MCP Server

cwtools-config

Retrieve Stellaris config rules (.cwt files) for modding, including specific files or entire directories from stable or development versions. Supports listing files for quick reference.

Instructions

CWToolsのStellarisコンフィグルール(.cwt files)を取得します。最新の安定版または開発版から、config/直下およびサブディレクトリ(common/等)内の特定のファイルまたは全ファイルを取得できます。

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
fileNo取得する特定の.cwtファイル名またはパス (例: common.cwt, common/agreements.cwt)
listOnlyNoファイル一覧のみを表示する場合はtrue
versionNo取得するバージョン: stable (最新タグ) または latest (開発版)stable

Implementation Reference

  • The main handler function for the 'cwtools-config' tool. Fetches .cwt config files from cwtools/cwtools-stellaris-config GitHub repo. Supports stable/latest versions, specific file fetch, list-only mode, with comprehensive retry logic, directory traversal, and formatted Markdown output.
      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}`);
        }
      },
  • Zod schema defining input parameters for the tool: file (optional string), listOnly (boolean default false), version (enum stable/latest default stable).
    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 (開発版)"),
    }),
  • src/server.ts:438-761 (registration)
    Registers the 'cwtools-config' tool with the FastMCP server instance, including annotations, description, handler, name, and parameters schema.
    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 (開発版)"),
      }),
    });
  • Helper function to retrieve directory contents from GitHub API, crucial for listing config files and subdirectories in cwtools-config.
    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;
    }
  • Helper function to fetch raw file content from GitHub API (handles base64 decoding), used to retrieve .cwt file contents.
    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;
    }
Install Server

Other Tools

Related Tools

Latest Blog Posts

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