UNPKG

windsurfmcp-aws-mcp-server1

Version:

Claude Desktop用のAWS MCPサーバーパッケージです。WebSocketを通じてAWS上のLambda関数に接続し、ZohoのCRM/Desk/Books APIとの連携機能を提供します。

415 lines (366 loc) 15.7 kB
import { logger } from "../utils/logger.mjs"; import { zohoConfig } from "../config/zoho-config.mjs"; import { tokenManager } from "../utils/token-manager.mjs"; /** * ZohoCRM APIクライアントクラス */ class ZohoCRMClient { constructor() { this.service = "crm"; } /** * 接続状態を確認 * @returns {Promise<boolean>} 接続状態 */ async checkConnection() { try { logger.info("ZohoCRM接続状態を確認します"); // アクセストークンを取得(自動更新機能付き) const accessToken = await tokenManager.getAccessToken(this.service); if (!accessToken) { logger.warn("ZohoCRMの有効なアクセストークンがありません"); return false; } // ユーザー情報を取得して接続テスト const endpoint = "/users?type=CurrentUser"; const response = await this.makeRequest(endpoint); if (response && response.users && response.users.length > 0) { logger.info("ZohoCRMに正常に接続されています"); return true; } else { logger.warn("ZohoCRMからユーザー情報を取得できませんでした"); return false; } } catch (error) { logger.error("ZohoCRM接続確認中にエラーが発生しました", error); return false; } } /** * APIリクエストを実行 * @param {string} endpoint - APIエンドポイント * @param {object} options - リクエストオプション * @returns {Promise<object>} レスポンスデータ */ async makeRequest(endpoint, options = {}) { try { // アクセストークンを取得(自動更新機能付き) const accessToken = await tokenManager.getValidAccessToken(this.service); if (!accessToken) { throw new Error("有効なアクセストークンがありません"); } // トークン情報からAPIドメインを取得 const tokens = tokenManager.getTokens(this.service); // トークンまたは設定ファイルからAPIドメインを取得 const apiBaseUrl = tokens?.api_domain || zohoConfig.getApiBaseUrl(this.service); logger.info(`Zoho CRM APIドメインを取得: ${apiBaseUrl}`); // JPDCデータセンターを使用 if (apiBaseUrl.includes("zoho.jp")) { logger.info(`JPDCデータセンターを使用します: ${apiBaseUrl}`); } // エンドポイントの先頭のスラッシュを削除 const cleanEndpoint = endpoint.startsWith("/") ? endpoint.slice(1) : endpoint; // リクエストURLを構築(APIバージョンv2を使用) const url = `${apiBaseUrl}/crm/v2/${cleanEndpoint}`; // デバッグ情報を出力 logger.debug(`APIリクエストURL: ${url}`); logger.debug(`リクエストメソッド: ${options.method || "GET"}`); // リクエストオプションを設定 const requestOptions = { ...options, headers: { ...options.headers, "Authorization": `Zoho-oauthtoken ${accessToken}`, "Content-Type": "application/json" } }; // リクエストを実行 logger.info(`${url} へリクエストを実行します`); const response = await fetch(url, requestOptions); // レスポンスを解析 const responseText = await response.text(); logger.debug(`レスポンステキスト: ${responseText.substring(0, 200)}...`); // HTMLレスポンスの場合はエラーとして処理 if (responseText.trim().startsWith("<!doctype html>") || responseText.trim().startsWith("<html>")) { logger.error("APIがHTMLレスポンスを返しました。APIエンドポイントが正しくない可能性があります。"); logger.error(`使用したURL: ${url}`); return { data: [], info: { count: 0, page: 1, perPage: 200, moreRecords: false } }; } // JSONとしてパース let data; try { data = JSON.parse(responseText); } catch (parseError) { logger.error(`JSONパースエラー: ${parseError.message}`); logger.error(`パース失敗したテキスト: ${responseText}`); return { data: [], info: { count: 0, page: 1, perPage: 200, moreRecords: false } }; } // エラーチェック if (!response.ok) { const errorMessage = data.message || response.statusText; logger.error(`APIエラー: ${errorMessage}`, data); return { data: [], info: { count: 0, page: 1, perPage: 200, moreRecords: false } }; } return data; } catch (error) { logger.error(`APIリクエストエラー: ${error.message}`, error); return { data: [], info: { count: 0, page: 1, perPage: 200, moreRecords: false } }; } } /** * モジュールのレコードを取得 * @param {string} module - モジュール名 * @param {object} params - クエリパラメータ * @returns {Promise<object>} レコード一覧 */ async getRecords(module, params = {}) { logger.info(`${module}のレコードを取得します`); try { // クエリパラメータを構築 const queryParams = new URLSearchParams(params); logger.debug("クエリパラメータ:", queryParams.toString()); // パラメータをマージ const mergedParams = { ...params }; logger.debug("マージ後のパラメータ:", mergedParams); // エンドポイントを構築 const endpoint = `${module}?${queryParams.toString()}`; logger.debug("最終的なエンドポイント:", endpoint); // APIリクエストURLを構築 const apiUrl = `${zohoConfig.getApiBaseUrl(this.service)}/${endpoint}`; logger.debug("APIリクエストURL:", apiUrl); // リクエストメソッドを設定 const method = "GET"; logger.debug("リクエストメソッド:", method); // リクエストを実行 logger.info(`${apiUrl} へリクエストを実行します`); const response = await this.makeRequest(endpoint, { method }); // レスポンスを処理 if (response.data) { logger.info(`${module}から${response.data.length}件のレコードを取得しました`); return response; } return { data: [] }; } catch (error) { logger.error(`${module}のレコード取得中にエラーが発生しました:`, error); throw error; } } /** * 特定のレコードを取得 * @param {string} module - モジュール名 * @param {string} recordId - レコードID * @returns {Promise<object>} レコード詳細 */ async getRecord(module, recordId) { try { logger.info(`${module}のレコード(ID: ${recordId})を取得します`); const endpoint = `/${module}/${recordId}`; const response = await this.makeRequest(endpoint); return response; } catch (error) { logger.error(`${module}のレコード(ID: ${recordId})の取得に失敗しました`, error); throw error; } } /** * レコードを作成 * @param {string} module - モジュール名 * @param {object} data - レコードデータ * @returns {Promise<object>} 作成されたレコード */ async createRecord(module, data) { try { logger.info(`${module}に新しいレコードを作成します`); const endpoint = `/${module}`; const response = await this.makeRequest(endpoint, { method: "POST", body: JSON.stringify({ data: [data] }) }); return response; } catch (error) { logger.error(`${module}のレコード作成に失敗しました`, error); throw error; } } /** * レコードを更新 * @param {string} module - モジュール名 * @param {string} recordId - レコードID * @param {object} data - 更新データ * @returns {Promise<object>} 更新されたレコード */ async updateRecord(module, recordId, data) { try { logger.info(`${module}のレコード(ID: ${recordId})を更新します`); const endpoint = `/${module}/${recordId}`; const response = await this.makeRequest(endpoint, { method: "PUT", body: JSON.stringify({ data: [data] }) }); return response; } catch (error) { logger.error(`${module}のレコード(ID: ${recordId})の更新に失敗しました`, error); throw error; } } /** * レコードを削除 * @param {string} module - モジュール名 * @param {string} recordId - レコードID * @returns {Promise<object>} 削除結果 */ async deleteRecord(module, recordId) { try { logger.info(`${module}のレコード(ID: ${recordId})を削除します`); const endpoint = `/${module}/${recordId}`; const response = await this.makeRequest(endpoint, { method: "DELETE" }); return response; } catch (error) { logger.error(`${module}のレコード(ID: ${recordId})の削除に失敗しました`, error); throw error; } } /** * 検索クエリを実行 * @param {string} module - モジュール名 * @param {string} criteria - 検索条件 * @returns {Promise<object>} 検索結果 */ async searchRecords(module, criteria) { try { logger.info(`${module}のレコードを検索します`); const endpoint = `/${module}/search`; const response = await this.makeRequest(endpoint, { method: "GET", params: { criteria } }); return response; } catch (error) { logger.error(`${module}のレコード検索に失敗しました`, error); throw error; } } /** * モジュールのメタデータを取得 * @param {string} module - モジュール名 * @returns {Promise<object>} メタデータ */ async getModuleMetadata(module) { try { logger.info(`${module}のメタデータを取得します`); const endpoint = `/settings/modules/${module}`; const response = await this.makeRequest(endpoint); return response; } catch (error) { logger.error(`${module}のメタデータ取得に失敗しました`, error); throw error; } } /** * モジュールからデータを取得 * @param {string} module - モジュール名 * @param {object} filters - 検索フィルター * @returns {Promise<Array>} データ一覧 */ async getData(module, filters = {}) { try { const moduleStr = typeof module === "string" ? module : String(module); logger.info(`${moduleStr}のデータを取得します`); // フィルターをクエリパラメータに変換 const queryParams = { // フィールドパラメータを直接転送 fields: filters.fields }; // フィルター処理 if (Object.keys(filters).length > 0) { // fieldsパラメータを除外して検索条件を構築 const { fields, ...searchFilters } = filters; // Zoho CRM APIの検索条件形式に変換 const criteria = Object.entries(searchFilters) .map(([key, value]) => { if (typeof value === "string") { return `${key}:equals:${value}`; } else if (Array.isArray(value)) { return `${key}:in:${value.join(",")}`; } else if (typeof value === "object") { if (value.operator && value.value) { return `${key}:${value.operator}:${value.value}`; } } return null; }) .filter(Boolean) .join(" and "); if (criteria) { queryParams.criteria = criteria; } } // データ取得 logger.debug("使用するクエリパラメータ:", queryParams); const response = await this.getRecords(moduleStr, queryParams); // レスポンスからデータを抽出して整形 const result = { data: response.data || [], info: { count: response.info ? response.info.count : 0, page: response.info ? response.info.page : 1, perPage: response.info ? response.info.per_page : 10, moreRecords: response.info ? response.info.more_records : false } }; logger.info(`${moduleStr}から${result.data.length}件のデータを取得しました`); return result; } catch (error) { logger.error(`${module}のデータ取得に失敗しました: ${error.message}`, error); return { data: [], info: { count: 0, page: 1, perPage: 10, moreRecords: false } }; } } } // シングルトンインスタンスをエクスポート const zohoCRMClient = new ZohoCRMClient(); export { zohoCRMClient }; export default { zohoCRMClient };