windsurfmcp-aws-mcp-server1
Version:
Claude Desktop用のAWS MCPサーバーパッケージです。WebSocketを通じてAWS上のLambda関数に接続し、ZohoのCRM/Desk/Books APIとの連携機能を提供します。
415 lines (366 loc) • 15.7 kB
JavaScript
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 };