#
tokens: 15755/50000 22/22 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── icon-small.png
├── icon.png
├── LICENSE.txt
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── README.md
├── src
│   ├── actions
│   │   └── index.ts
│   ├── constants
│   │   └── index.ts
│   ├── index.ts
│   ├── mcp
│   │   └── index.ts
│   ├── openApiClient
│   │   └── index.ts
│   ├── resources
│   │   └── initResources.ts
│   ├── tools
│   │   ├── callTool.ts
│   │   └── utils
│   │       ├── convertTimestamps.ts
│   │       └── toTimestamps.ts
│   ├── types
│   │   ├── action.ts
│   │   ├── alibabaCloudApi.ts
│   │   └── common.ts
│   └── utils
│       ├── common.ts
│       ├── getDataWorksMcp.ts
│       ├── getDataWorksPopMcpTools.ts
│       ├── initDataWorksTools.ts
│       ├── initExtraTools.ts
│       ├── record.ts
│       └── zodToMCPSchema.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
node_modules
build

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
[![MseeP.ai Security Assessment Badge](https://mseep.net/mseep-audited.png)](https://mseep.ai/app/aliyun-alibabacloud-dataworks-mcp-server)

# DataWorks MCP Server

A Model Context Protocol (MCP) server that provides tools for AI, allowing it to interact with the DataWorks Open API through a standardized interface. This implementation is based on the Aliyun Open API and enables AI agents to perform cloud resources operations seamlessly.

## Overview

This MCP server:

* Interact with DataWorks Open API
* Manage DataWorks resources

The server implements the Model Context Protocol specification to standardize cloud resource interactions for AI agents.

## Prerequisites

* Node.js (v16 or higher)
* pnpm (recommended), npm, or yarn
* DataWorks Open API with access key and secret key

## Installation

### Option 1: Install from npm (recommend for clients like Cursor/Cline)

```bash
# Install globally
npm install -g alibabacloud-dataworks-mcp-server

# Or install locally in your project
npm install alibabacloud-dataworks-mcp-server
```

### Option 2: Build from Source (for developers)

1. Clone this repository:
```bash
git clone https://github.com/aliyun/alibabacloud-dataworks-mcp-server
cd alibabacloud-dataworks-mcp-server
```

2. Install dependencies (pnpm is recommended, npm is supported):
```bash
pnpm install
```

3. Build the project:
```bash
pnpm run build
```

4. Development the project (by @modelcontextprotocol/inspector):
```bash
pnpm run dev
```
open http://localhost:5173

## Configuration

### MCP Server Configuration

If you installed via npm (Option 1):
```json
{
  "mcpServers": {
    "alibabacloud-dataworks-mcp-server": {
      "command": "npx",
      "args": ["alibabacloud-dataworks-mcp-server"],
      "env": {
        "REGION": "your_dataworks_open_api_region_id_here",
        "ALIBABA_CLOUD_ACCESS_KEY_ID": "your_alibaba_cloud_access_key_id",
        "ALIBABA_CLOUD_ACCESS_KEY_SECRET": "your_alibaba_cloud_access_key_secret",
        "TOOL_CATEGORIES": "optional_your_tool_categories_here_ex_UTILS",
        "TOOL_NAMES": "optional_your_tool_names_here_ex_ListProjects"
      },
      "disabled": false,
      "autoApprove": []
    }
  }
}
```

If you built from source (Option 2):
```json
{
  "mcpServers": {
    "alibabacloud-dataworks-mcp-server": {
      "command": "node",
      "args": ["/path/to/alibabacloud-dataworks-mcp-server/build/index.js"],
      "env": {
        "REGION": "your_dataworks_open_api_region_id_here",
        "ALIBABA_CLOUD_ACCESS_KEY_ID": "your_alibaba_cloud_access_key_id",
        "ALIBABA_CLOUD_ACCESS_KEY_SECRET": "your_alibaba_cloud_access_key_secret",
        "TOOL_CATEGORIES": "optional_your_tool_categories_here_ex_SERVER_IDE_DEFAULT",
        "TOOL_NAMES": "optional_your_tool_names_here_ex_ListProjects"
      },
      "disabled": false,
      "autoApprove": []
    }
  }
}
```

### Environment Setup

init variables in your environment:

```env
# DataWorks Configuration
REGION=your_dataworks_open_api_region_id_here
ALIBABA_CLOUD_ACCESS_KEY_ID=your_alibaba_cloud_access_key_id
ALIBABA_CLOUD_ACCESS_KEY_SECRET=your_alibaba_cloud_access_key_secret
TOOL_CATEGORIES=optional_your_tool_categories_here_ex_SERVER_IDE_DEFAULT
TOOL_NAMES=optional_your_tool_names_here_ex_ListProjects
```

### Configuration Description
- Use Guide Description [Link](https://www.alibabacloud.com/help/dataworks/user-guide/dataworks-mcp-server-function-usage#1ecf2a04b5ilh)

## Project Structure

```
alibabacloud-dataworks-mcp-server/
├── src/
│   ├── index.ts          # Main entry point
├── package.json
└── tsconfig.json
```

## Available Tools

The MCP server provides the following DataWorks tools:

See this [link](https://dataworks.data.aliyun.com/dw-pop-mcptools)

## Security Considerations

* Keep your private key secure and never share it
* Use environment variables for sensitive information
* Regularly monitor and audit AI agent activities

## Troubleshooting

If you encounter issues:

1. Verify your Aliyun Open API access key and secret key are correct
2. Check your region id is correct
3. Ensure you're on the intended network (mainnet, testnet, or devnet)
4. Verify the build was successful

## Dependencies

Key dependencies include:
* [@alicloud/dataworks-public20240518](https://github.com/alibabacloud-sdk-swift/dataworks-public-20240518)
* [@alicloud/openapi-client](https://github.com/aliyun/darabonba-openapi)

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

This project is licensed under the Apache 2.0 License.

```

--------------------------------------------------------------------------------
/src/types/common.ts:
--------------------------------------------------------------------------------

```typescript
export interface Resource {
  uri?: string;
  name?: string;
  description?: string;
  mimeType?: 'text/json' | 'text/plain' | 'image/png';
}

/** mcp 接口返回 */
export interface DataWorksMCPResponse {
  jsonrpc: string;
  id: string;
  result: {
    /** resource 白名单 */
    a2reslist: string[];
  };
}

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}
```

--------------------------------------------------------------------------------
/src/tools/utils/toTimestamps.ts:
--------------------------------------------------------------------------------

```typescript
import dayjs from 'dayjs';
import { OpenApiClientInstance } from "../../openApiClient/index.js";

export default async function toTimestamps(
  agent: OpenApiClientInstance,
  dateTimeDisplay?: string[],
) {
  try {
    const result: number[] = [];
    if (dateTimeDisplay) {
      dateTimeDisplay?.forEach?.((str) => {
        try {
          const timestamp = dayjs(str).valueOf();
          result.push(timestamp);
        } catch (e) {
          console.error(e);
        }
      });
    }
    return result;
  } catch (error: any) {
    throw new Error(`To timestamps failed: ${error.message}`);
  }
}

```

--------------------------------------------------------------------------------
/src/tools/utils/convertTimestamps.ts:
--------------------------------------------------------------------------------

```typescript
import dayjs from 'dayjs';
import { OpenApiClientInstance } from "../../openApiClient/index.js";

export default async function convertTimestamps(
  agent: OpenApiClientInstance,
  timestamps?: number[],
  format?: string,
) {
  try {
    const result: string[] = [];
    if (timestamps) {
      if (format) {
        timestamps?.forEach?.((timestamp) => {
          try {
            const date = new Date(timestamp);
            const display = dayjs(date).format(format || 'YYYY-MM-DD');
            result.push(display);
          } catch (e) {
            console.error(e);
          }
        });
      };
    }
    return result;
  } catch (error: any) {
    throw new Error(`Convert timestamps failed: ${error.message}`);
  }
}

```

--------------------------------------------------------------------------------
/src/utils/record.ts:
--------------------------------------------------------------------------------

```typescript
import fetch from 'node-fetch';
import { dataWorksRecordUrl } from '../constants/index.js';

/** 记录是否失败 */
export default async function record(options: {
  success?: boolean;
  error?: string;
  toolName?: string;
  resourceUri?: string;
  version?: string;
  requestId?: string;
} = {}) {
  try {
    await fetch(`${dataWorksRecordUrl}?method=report&requestId=${encodeURIComponent(options?.requestId || '')}&error=${encodeURIComponent(String(options?.error || ''))}&api=${encodeURIComponent(options?.toolName || '')}&type=${encodeURIComponent(options?.resourceUri ? 'resource' : options?.toolName ? 'tool' : '')}&resourceUri=${encodeURIComponent(options?.resourceUri || '')}&version=${encodeURIComponent(options?.version || '')}&success=${encodeURIComponent(options?.success || '')}&isInner=false`);
    console.debug('Success record');
  } catch (e) {
    console.error('Failed to record:', e);
  }
}
```

--------------------------------------------------------------------------------
/src/utils/getDataWorksMcp.ts:
--------------------------------------------------------------------------------

```typescript
import fs from 'fs';
import fetch from 'node-fetch';
import { isPreMode, parseJSONString } from "./common.js";
import { dataWorksMcpUrl, dataWorksPreMcpUrl } from '../constants/index.js';
import { DataWorksMCPResponse } from '../types/common.js';

/** 获取 dw mcp 接口 */
export default async function getDataWorksMcp(options?: {}) {
  const isPre = isPreMode();

  // 如果是预发环境,支持本地文件
  const fileUri = process.env.MCP_FILE_URI || (isPre ? dataWorksPreMcpUrl : dataWorksMcpUrl);

  let dwMcpRes;
  try {
    if (!fileUri?.startsWith?.('http')) {
      // local file
      const fileContent = fs.readFileSync(fileUri, 'utf8');
      dwMcpRes = parseJSONString(fileContent);
    } else {
      // http file
      const queryRes = await fetch(fileUri);
      const resStr = await queryRes.text() as string;
      dwMcpRes = parseJSONString(resStr) as DataWorksMCPResponse;
    }
  } catch (e) {
    console.error('Failed to get getDataWorksMcp:', e);
  }
  return dwMcpRes;
}
```

--------------------------------------------------------------------------------
/src/actions/index.ts:
--------------------------------------------------------------------------------

```typescript
import OpenApiClientInstance from "../openApiClient/index.js";
import callTool from "../tools/callTool.js";
import { ActionTool } from "../types/action.js";

export const getHandler = (apiKey: string, actionTool: ActionTool) => async (agent: OpenApiClientInstance, input: Record<string, any>) => {
  try {

    const response = await callTool(agent, apiKey, actionTool, input);
    return response;

  } catch (error: any) {
    // Handle specific Perplexity API error types
    if (error.response) {
      const { status, data } = error.response;
      if (status === 429) {
        return {
          statusCode: status,
          body: "Error: Rate limit exceeded. Please try again later.",
        };
      }
      return {
        statusCode: status,
        body: `Error: ${data.error?.message || error.message}`,
      };
    }

    return {
      body: `Failed to get information: ${error.message}`,
    };
  }
};

export type { ActionTool, ActionExample, Handler } from "../types/action.js";

```

--------------------------------------------------------------------------------
/src/utils/zodToMCPSchema.ts:
--------------------------------------------------------------------------------

```typescript

import { z } from "zod";

// Define the raw shape type that MCP tools expect
export type MCPSchemaShape = {
  [key: string]: z.ZodType<any>;
};

// Type guards for Zod schema types
function isZodOptional(schema: z.ZodTypeAny): schema is z.ZodOptional<any> {
  return schema instanceof z.ZodOptional;
}

function isZodObject(schema: z.ZodTypeAny): schema is z.ZodObject<any> {
  // Check both instanceof and the typeName property
  return (
    schema instanceof z.ZodObject ||
    (schema?._def?.typeName === 'ZodObject')
  );
}

/**
 * Converts a Zod object schema to a flat shape for MCP tools
 * @param schema The Zod schema to convert
 * @returns A flattened schema shape compatible with MCP tools
 * @throws Error if the schema is not an object type
 */
export function zodToMCPShape(schema: z.ZodTypeAny): { result: MCPSchemaShape, keys: string[] } {
  if (!isZodObject(schema)) {
    throw new Error("MCP tools require an object schema at the top level");
  }

  const shape = schema.shape;
  const result: MCPSchemaShape = {};

  for (const [key, value] of Object.entries(shape)) {
    result[key] = isZodOptional(value as any) ? (value as any).unwrap() : value;
  }

  return {
    result,
    keys: Object.keys(result)
  };
}

```

--------------------------------------------------------------------------------
/src/types/action.ts:
--------------------------------------------------------------------------------

```typescript
import { OpenApiClientInstance } from "../openApiClient/index.js";
import { z } from "zod";
import { ApiMethodUpperCase, ApiParameter, ApiParameterSchema } from "./alibabaCloudApi.js";

/**
 * Example of an action with input and output
 */
export interface ActionExample {
  input: Record<string, any>;
  output: Record<string, any>;
  explanation: string;
}

/**
 * Handler function type for executing the action
 */
export type Handler = (
  agent: OpenApiClientInstance,
  input: Record<string, any>,
) => Promise<Record<string, any>>;

export interface DwInputSchema extends Omit<ApiParameterSchema, 'required' | 'properties' | 'items'> {
  /** 有 properties 时,required 为 string[] */
  required?: string[];
  items?: DwInputSchema;
  properties?: { [name: string]: DwInputSchema };
}

/**
 * Cline 的市集应用在 Tool 上包了一个 Action,做进一步扩展
 * Main Action interface inspired by ELIZA
 * This interface makes it easier to implement actions across different frameworks
 */
export interface ActionTool {
  /**
   * Unique name of the action
   */
  name: string;

  /**
   * Detailed description of what the action does
   */
  description?: string;

  /**
   * 直接写好 schema。
   * https://modelcontextprotocol.io/docs/concepts/tools
   */
  inputSchema?: DwInputSchema;

  annotations?: {
    path?: string;
    method?: ApiMethodUpperCase;
    /** ex 2024-05-18 */
    version?: string;
    example?: string;
    category?: string;
    pmd: {
      [name: string]: ApiParameter;
    };
  };

  // --------------- 以下为扩展 -----------------

  /**
   * Alternative names/phrases that can trigger this action
   */
  similes?: string[];

  /**
   * Array of example inputs and outputs for the action
   * Each inner array represents a group of related examples
   */
  examples?: ActionExample[][];

  /**
   * Zod schema for input validation
   */
  schema?: z.ZodType<any>;

  /**
   * Function that executes the action
   */
  handler?: Handler;

  /**
   * 有对应的 MCP Resource
   */
  hasMcpResource?: boolean;
}

```

--------------------------------------------------------------------------------
/src/utils/getDataWorksPopMcpTools.ts:
--------------------------------------------------------------------------------

```typescript
import fs from 'fs';
import fetch from 'node-fetch';
import { ActionTool } from "../types/action.js";
import { isPreMode, parseJSONString } from "./common.js";
import { dataWorksPopMcpToolsUrl, dataWorksPrePopMcpToolsUrl } from '../constants/index.js';

export default async function getDataWorksPopMcpTools(options?: { categories?: string[]; names?: string[]; }) {
  const isPre = isPreMode();
  // 如果是预发环境,支持本地文件
  const toolFileUri = process.env.TOOL_FILE_URI || (isPre ? dataWorksPrePopMcpToolsUrl : dataWorksPopMcpToolsUrl);

  let dataWorksPopMcpTools: ActionTool[] = [];
  try {
    if (!toolFileUri?.startsWith?.('http')) {
      // local file
      const fileContent = fs.readFileSync(toolFileUri, 'utf8');
      dataWorksPopMcpTools = parseJSONString(fileContent);

      // 如果有传入 categories 只挑有列的
      const categories = (options?.categories || process.env?.TOOL_CATEGORIES?.split?.(',')) || [];
      if (categories?.length) {
        dataWorksPopMcpTools = dataWorksPopMcpTools.filter((item) => {
          return categories?.includes(item?.annotations?.category || '');
        });
      }

      // 如果有传入 names 只挑有列的
      const names = (options?.names || process.env?.TOOL_NAMES?.split?.(',')) || [];
      if (names?.length) {
        dataWorksPopMcpTools = dataWorksPopMcpTools.filter((item) => {
          return names?.includes(item?.name || '');
        });
      }

    } else {
      // http file

      // 接口过滤
      const categories = (options?.categories?.join?.(',') || process.env?.TOOL_CATEGORIES) || '';
      const names = (options?.names?.join?.(',') || process.env?.TOOL_NAMES) || '';
      let _params = '';
      if (categories) _params += `categories=${encodeURIComponent(categories)}`;
      if (names) _params += `${_params ? '&' : ''}names=${encodeURIComponent(names)}`;
      if (_params) _params = `?${_params}`;
      const queryRes = await fetch(`${toolFileUri}${_params}`);
      const queryResStr = await queryRes.text() as string;
      dataWorksPopMcpTools = parseJSONString(queryResStr) as ActionTool[];
    }
  } catch (e) {
    console.error('Failed to get dataWorksPopMcpTools:', e);
  }
  return dataWorksPopMcpTools;
}
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "alibabacloud-dataworks-mcp-server",
  "version": "1.0.43",
  "description": "DataWorks MCP Server: Export the DataWorks Open API to MCP Server, allowing clients that can run MCP Server to use DataWorks Open API through AI.",
  "main": "build/index.js",
  "module": "build/index.js",
  "type": "module",
  "icon": "icon.png",
  "bin": {
    "alibabacloud-dataworks-mcp-server": "./build/index.js"
  },
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
    "start": "pnpm run build; REGION=cn-shanghai node build/index.js",
    "dev": "pnpm run build; npx @modelcontextprotocol/inspector -e NODE_ENV=development -e REGION=cn-shanghai -e ALIBABA_CLOUD_ACCESS_KEY_ID=your_aliyun_key_id -e ALIBABA_CLOUD_ACCESS_KEY_SECRET=your_aliyun_key_secret node build/index.js",
    "restart": "pnpm run build; REGION=cn-shanghai node build/index.js",
    "pre-start": "pnpm run build; REGION=cn-shanghai NODE_ENV=development VERBOSE=true node build/index.js"
  },
  "files": [
    "build"
  ],
  "repository": {
    "type": "git",
    "url": "[email protected]:aliyun/alibabacloud-dataworks-mcp-server.git"
  },
  "keywords": [
    "alibaba",
    "aliyun",
    "cloud",
    "computing",
    "dataworks",
    "big data",
    "mcp",
    "ai",
    "open api",
    "dataworks-mcp"
  ],
  "author": "DataWorks",
  "license": "Apache-2.0",
  "dependencies": {
    "@alicloud/credentials": "2.4.2",
    "@alicloud/dataworks-public20240518": "^6.0.2",
    "@alicloud/openapi-client": "^0.4.13",
    "@alicloud/openapi-util": "^0.3.2",
    "@alicloud/tea-typescript": "^1.8.0",
    "@alicloud/tea-util": "^1.4.10",
    "@modelcontextprotocol/sdk": "^1.5.0",
    "@modelcontextprotocol/server-filesystem": "2025.3.28",
    "bignumber.js": "^9.1.0",
    "dayjs": "^1.11.13",
    "dotenv": "^16.4.7",
    "express": "4.20.0",
    "lodash": "^4.17.21",
    "lossless-json": "^4.0.2",
    "node-fetch": "3.3.2",
    "openai": "^4.77.0",
    "path": "^0.12.7",
    "uuid": "9.0.1",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@anthropic-ai/sdk": "^0.39.0",
    "@modelcontextprotocol/inspector": "^0.6.0",
    "@types/bignumber.js": "^5.0.4",
    "@types/lodash": "^4.17.16",
    "@types/node": "^22.13.4",
    "tailwindcss": "^3.0.0",
    "typescript": "^5.7.3"
  },
  "resolutions": {
    "@alicloud/credentials": "2.4.2",
    "uuid": "9.0.1"
  },
  "packageManager": "[email protected]",
  "publishConfig": {
    "registry": "https://registry.npmjs.org"
  },
  "homepage": "https://github.com/aliyun/alibabacloud-dataworks-mcp-server.git",
  "bugs": {
    "url": "https://github.com/aliyun/alibabacloud-dataworks-mcp-server/issues",
    "mail": ""
  },
  "tnpm": {
    "lockfile": "enable",
    "mode": "npm"
  }
}
```

--------------------------------------------------------------------------------
/src/resources/initResources.ts:
--------------------------------------------------------------------------------

```typescript
import record from '../utils/record.js';
import { ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { isVerboseMode, getEnvInfo, getMcpResourceName, toJSONString } from '../utils/common.js';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { ActionTool } from '../types/action.js';
import { DataWorksMCPResponse } from '../types/common.js';

/**
 * init resources of this MCP server
 */
async function initResources(
  server: McpServer['server'],
  dataWorksPopMcpTools: ActionTool[],
  dataworksMcpRes?: DataWorksMCPResponse,
) {

  try {

    // Resource file

    // DW 资源的白名单
    const dwWhiteList: string[] = dataworksMcpRes?.result?.a2reslist || [];

    server?.setRequestHandler(ListResourcesRequestSchema, async () => {
      const resourceList = dataWorksPopMcpTools?.filter((item) => dwWhiteList?.includes?.(item?.name))?.map?.((item) => {
        return {
          uri: item?.name,
          name: getMcpResourceName({ toolName: item?.name }),
          description: `${item?.name}的定义详情,如接口返回范例描述,输入参数范例等`,
          mimeType: "text/json",
        }
      }) || [];
      return {
        resources: resourceList,
      };
    });

    server?.setRequestHandler?.(ReadResourceRequestSchema, async (request) => {
      const uri = request?.params?.uri;
      try {
        if (uri?.startsWith?.('http')) {
          const res = await fetch(uri);
          const jsonStr = await res.text() || '{}'; // 不使用 .json(),可能会丢失精度

          await record({ success: true, resourceUri: uri });

          return {
            contents: [
              {
                uri,
                mimeType: "text/json",
                text: jsonStr,
              }
            ]
          };
        } else {

          const toolInfo = dataWorksPopMcpTools?.find?.((item) => {
            return item?.name === uri;
          });

          if (toolInfo) {

            await record({ success: true, resourceUri: uri });

            return {
              contents: [
                {
                  uri,
                  mimeType: "text/json",
                  text: toJSONString(toolInfo || {}),
                }
              ]
            };
          } else {
            throw new Error(`Resource not found. ${uri}`);
          }

        }
      } catch (e: any) {
        console.error(e);
        await record({ success: false, error: e?.message });
        throw new Error(`Resource not found. ${uri}`);
      }
    });

  } catch (error: any) {
    const verbose = isVerboseMode();
    const errorMessage = `init resources failed: ${error.message}, ${verbose ? `, env info: ${getEnvInfo()}` : ''}`;
    throw new Error(errorMessage);
  }
};

export default initResources;

```

--------------------------------------------------------------------------------
/src/types/alibabaCloudApi.ts:
--------------------------------------------------------------------------------

```typescript

/** https://api.aliyun.com/openmeta/struct/ApiDocs */


export type ApiParameterType = 'integer' | 'string' | 'date' | 'boolean' | 'array' | 'object';
export type ApiParameterFormat = 'int32' | 'int64';

interface ApiDirectory {
  id: number;
  title: string;
  type: 'directory';
  children: (string | ApiDirectory)[];
}

interface ApiProperty {
  id: number;
  title: string;
  type: 'directory';
  format: 'int64';
  example: string;
}

interface ApiSchema {
  title: string;
  description?: string;
  type: 'object';
  properties?: { [name: string]: ApiProperty };
}

export type ApiMethod = 'get' | 'post' | 'delete' | 'put';
export type ApiMethodUpperCase = 'GET' | 'POST' | 'DELETE' | 'PUT';
type ApiScheme = 'http' | 'https';
type ApiSecurity = {
  AK: []
};

export interface ApiParameterSchema {
  description?: string;
  type?: ApiParameterType;
  items?: ApiParameterSchema;
  properties?: { [name: string]: ApiParameterSchema };
  format?: ApiParameterFormat;
  required?: boolean;
  example?: string;
}

export interface ApiParameter {
  name?: string;
  /** https://help.aliyun.com/zh/sdk/developer-reference/generalized-call-node-js */
  in: 'query' | 'body' | 'formData' | 'byte';
  /** 【style=repeatList】时,数组的序列化方式为XXX.N的形式,例如:Instance.1=i-instance1&Instance.2=i-instance2,  需要配置元素最小值,最大值,根据需要开启repeatList参数校验,连续性校验 */
  style: 'json' | 'repeatList';
  schema?: ApiParameterSchema;
}

interface ApiResponseSchemaProperty {
  title: string;
  description: string;
  type: 'integer';
  format: 'int64';
  example: string;
}

interface ApiResponseSchema {
  title: string;
  description: string;
  type: 'object';
  properties: { [name: string]: ApiResponseSchemaProperty };
}

interface ApiResponse {
  schema: ApiResponseSchema;
}

export interface ApiObj {
  title: string;
  description?: string;
  summary: string;
  methods: ApiMethod[];
  schemes: ApiScheme[];
  security: ApiSecurity[];
  deprecated: boolean;
  systemTags: { [name: string]: any };
  parameters: ApiParameter[];
  responses: { [code: string]: ApiResponse };
  staticInfo: { [name: string]: any };
  responseDemo?: string;
}

interface ApiComponent {
  schemas: { [name: string]: ApiSchema };
  title: string;
  type: 'directory';
}

interface ApiEndpoint {
  regionId: string;
  endpoint: string;
}

export interface AlibabaCloudOpenApiInterface {
  version?: string;
  info?: {
    style: 'RPC';
    product: 'dataworks-public';
    version: string;
  };
  directories?: ApiDirectory[];
  /** 数据结构等信息 */
  components?: ApiComponent[];
  apis?: { [name: string]: ApiObj };
  endpoints?: ApiEndpoint[];
}

export interface IAlibabaCloudOpenApiJsonResponse {
  statusCode?: number;
  headers?: { [name: string]: string };
  body?: any;
}

export interface OpenApiConfigs {
  style: 'ROA' | 'RPC'; // API风格
  action: string; // API 名称
  version?: string; // API版本号
  protocol: 'HTTPS' | 'HTTP'; // API协议
  method?: ApiMethodUpperCase;// 请求方法
  authType: 'AK';
  pathname: string; // 接口 PATH
  reqBodyType?: 'formData' | 'byte' | 'json';// 接口请求体内容格式
  bodyType: 'binary' | 'array' | 'string' | 'json' | 'byte'; // 接口响应体内容格式
}
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import * as dotenv from "dotenv";
import OpenApiClient from "./openApiClient/index.js";
import initDataWorksTools from "./utils/initDataWorksTools.js";
import initExtraTools from "./utils/initExtraTools.js";
import initResources from "./resources/initResources.js";
import getDataWorksMcp from "./utils/getDataWorksMcp.js";
import getDataWorksPopMcpTools from "./utils/getDataWorksPopMcpTools.js";
import { startMcpServer } from "./mcp/index.js";
import { mcpServerVersion } from './constants/index.js';
import { ActionTool } from "./types/action.js";
import { ServerOptions } from "@modelcontextprotocol/sdk/server/index.js";

dotenv.config();

// Validate required environment variables
function validateEnvironment() {
  const requiredEnvVars = {};

  const missingVars = Object.entries(requiredEnvVars)
    .filter(([_, value]) => !value)
    .map(([key]) => key);

  if (missingVars.length > 0) {
    throw new Error(`Missing required environment variables: ${missingVars.join(', ')}`);
  }
}

async function main() {
  try {
    // Validate environment before proceeding
    validateEnvironment();

    // Initialize the agent with error handling
    const agent = await OpenApiClient.createClient();

    // 请求 dataworks mcp tools json
    const dataWorksPopMcpTools: ActionTool[] = await getDataWorksPopMcpTools();

    // 请求 dataworks mcp resources
    const dwMcpRes = await getDataWorksMcp();

    const mcpActions = initDataWorksTools(dataWorksPopMcpTools, dwMcpRes);

    // 增加额外定义的 tools
    const extraTools = initExtraTools() || {};
    Object.keys(extraTools).forEach((k) => {
      if (!mcpActions[k] && extraTools[k]) mcpActions[k] = extraTools[k];
    });

    const serverOptions: ServerOptions = {
      capabilities: {
        resources: {
          subscribe: true,
          listChanged: true,
        },
        tools: {},
      },
      instructions: 'Operating with DataWorks Open APIs',
    };

    // https://spec.modelcontextprotocol.io/specification/2024-11-05/server/utilities/logging/
    if (serverOptions.capabilities && process.env.LOGGING_LEVEL) {
      serverOptions.capabilities.logging = {
        level: process.env.LOGGING_LEVEL,
        logFile: process.env.LOG_FILE,
      };
    }

    console.log('dataworks-mcp starting...');

    // Start the MCP server with error handling
    const serverWrapper = await startMcpServer(mcpActions, agent, {
      name: "dataworks-agent",
      version: mcpServerVersion,
      serverOptions,
    });

    const server = serverWrapper?.server;

    // List available resources
    await initResources(server, dataWorksPopMcpTools, dwMcpRes);

  } catch (error) {
    console.error('Failed to start MCP server:', error instanceof Error ? error.message : String(error));
    process.exit(1);
  }
}

// Handle uncaught exceptions and rejections
process.on('uncaughtException', (error) => {
  console.error('Uncaught Exception:', error);
  process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  process.exit(1);
});

main();
```

--------------------------------------------------------------------------------
/src/utils/initExtraTools.ts:
--------------------------------------------------------------------------------

```typescript

import convertTimestamps from '../tools/utils/convertTimestamps.js';
import toTimestamps from '../tools/utils/toTimestamps.js';
import { ActionTool } from '../types/action.js';
import { convertInputSchemaToSchema } from './initDataWorksTools.js';

/** 增加一些额外的帮助 Tools */
const initExtraTools = (options?: { categories?: string[]; names?: string[]; }) => {

  const actionMap: { [name: string]: ActionTool } = {};

  try {

    // 将 timestamp 转成 date */
    let actionKey = 'ConvertTimestamps';
    let action: ActionTool = {
      name: actionKey,
      description: '将时间戳转成日期或时间。返回内容如果有时间戳,透过此Tool显示成为日期或时间。',
      schema: convertInputSchemaToSchema({
        type: 'object',
        properties: {
          Timestamps: {
            type: 'array',
            description: '时间戳数组',
            items: {
              type: 'integer',
              description: '时间戳,如:1743422516765',
            }
          },
          Format: {
            type: 'string',
            description: '日期格式,如:YYYY-MM-DD HH:mm:ss',
          },
        },
        required: ['Timestamps'],
      }),
      handler: async (agent, input) => {
        try {
          const { Timestamps, Format } = input;
          const response = await convertTimestamps(agent, Timestamps, Format);
          return response as any;
        } catch (error: any) {
          // Handle specific Perplexity API error types
          if (error.response) {
            const { status, data } = error.response;
            if (status === 429) {
              return {
                statusCode: status,
                body: "Error: Rate limit exceeded. Please try again later.",
              };
            }
            return {
              statusCode: status,
              body: `Error: ${data.error?.message || error.message}`,
            };
          }
          return {
            body: `Failed to get information: ${error.message}`,
          };
        }
      }
    };
    actionMap[actionKey] = action;

    // 将 display 转成 timestamp */
    actionKey = 'ToTimestamps';
    action = {
      name: actionKey,
      description: '将日期或时间转成时间戳。',
      schema: convertInputSchemaToSchema({
        type: 'object',
        properties: {
          DateTimeDisplay: {
            type: 'array',
            description: '日期或时间数组',
            items: {
              type: 'string',
              description: '日期或时间,如:2025-01-02 或 2025-01-01 12:11:00',
            }
          },
        },
        required: ['DateTimeDisplay'],
      }),
      handler: async (agent, input) => {
        try {
          const { DateTimeDisplay } = input;
          const response = await toTimestamps(agent, DateTimeDisplay);
          return response as any;
        } catch (error: any) {
          // Handle specific Perplexity API error types
          if (error.response) {
            const { status, data } = error.response;
            if (status === 429) {
              return {
                statusCode: status,
                body: "Error: Rate limit exceeded. Please try again later.",
              };
            }
            return {
              statusCode: status,
              body: `Error: ${data.error?.message || error.message}`,
            };
          }
          return {
            body: `Failed to get information: ${error.message}`,
          };
        }
      }
    };
    actionMap[actionKey] = action;

    return actionMap;
  } catch (e) {
    console.error(e);
    return {};
  }
};

export default initExtraTools;


```

--------------------------------------------------------------------------------
/src/utils/initDataWorksTools.ts:
--------------------------------------------------------------------------------

```typescript
import { getHandler } from '../actions/index.js';
import { ActionTool, DwInputSchema } from '../types/action.js';
import { z } from "zod";
import { ApiParameter, ApiParameterSchema } from '../types/alibabaCloudApi.js';
import { DataWorksMCPResponse } from '../types/common.js';

export const getZObjByType = (item?: ApiParameterSchema) => {

  let obj: any;

  const type = item?.type?.toLocaleLowerCase?.();

  if (type?.includes?.('int')) {
    // obj = z.bigint();
    obj = z.number();
  } if (type?.includes?.('double') || type?.includes?.('float')) {
    obj = z.number();
  } else if (type?.includes?.('string')) {
    obj = z.string();
  } else if (type?.includes?.('boolean')) {
    obj = z.boolean();
  } else if (type?.includes?.('date')) {
    obj = z.date();
  } else if (type?.includes?.('array')) {
    if (item?.items) {
      const childrenObjType = getZObjByType(item.items);
      obj = z.array(childrenObjType);
    } else obj = z.any();
  } else if (type?.includes?.('object')) {

    if (item?.properties) {
      const schema: { [name: string]: any } = {};
      Object.keys(item?.properties || {})?.forEach?.((paramName) => {
        const param = item?.properties?.[paramName];
        if (paramName) {
          let obj = getZObjByType(param);
          if (param?.description) obj = obj?.describe?.(param?.description);
          if (param?.required === false) {
            obj = obj?.optional?.();
            // 有 bug 需要执行两次
            if (obj?.optional) obj = obj?.optional?.();
          }
          schema[paramName] = obj;
        }
      });
      obj = z.object(schema);
    } else obj = z.any();

  } else {
    obj = z.any();
  }

  return obj;
}

export const convertInputSchemaToSchema = (inputSchema?: DwInputSchema, apiParameters?: ApiParameter[]) => {
  const schema: { [name: string]: any } = {};

  if (!inputSchema) return z.object(schema);

  const propertyKeys = Object.keys(inputSchema?.properties || {});
  const required = inputSchema?.required || [];

  // 先处理 required
  propertyKeys?.sort?.((a, b) => {
    if (required?.includes?.(a) && !required?.includes?.(b)) {
      return -1;
    } else if (!required?.includes?.(a) && required?.includes?.(b)) {
      return 1;
    } else {
      return 0;
    }
  });

  propertyKeys?.forEach?.((pKey) => {

    const info = inputSchema?.properties?.[pKey];
    const description = info?.description;

    let obj = getZObjByType(info as ApiParameterSchema);

    if (description) obj = obj?.describe?.(description);

    if (!required?.includes?.(pKey)) {
      obj = obj?.optional?.();
      // 有 bug 需要执行两次
      if (obj?.optional) obj = obj?.optional?.();
    }

    schema[pKey] = obj;

  });

  return z.object(schema);

};

/** 此方法是将 dw mcp 接口转成 action */
const initDataWorksTools = (dwTools: ActionTool[], dwMcpRes?: DataWorksMCPResponse) => {

  const actionMap: { [name: string]: ActionTool } = {};

  try {

    // DW 资源的白名单
    const dwWhiteList: string[] = dwMcpRes?.result?.a2reslist || [];

    dwTools?.forEach?.((t) => {
      const apiKey = t?.name;

      // 先过滤掉几个,方便调试
      // if (!['CreateDataServiceApi'].includes(apiKey)) return;

      if (apiKey) {

        const map: ActionTool = { ...t };

        if (t?.inputSchema && !t?.schema) {
          const apiMeta = (Object.keys(t?.annotations?.pmd || {})?.map?.((apiKey) => (t?.annotations?.pmd?.[apiKey])) || []) as ApiParameter[];
          map.schema = convertInputSchemaToSchema(t?.inputSchema, apiMeta);
        }

        if (!map?.handler) {
          map.handler = getHandler(apiKey, t) as any;
        }

        // 查看是否有对应的 MCP Resource
        map.hasMcpResource = dwWhiteList?.includes?.(t?.name);

        actionMap[t?.name] = map;

      }

    });

    return actionMap;
  } catch (e) {
    console.error(e);
    return {};
  }
};

export default initDataWorksTools;


```

--------------------------------------------------------------------------------
/src/utils/common.ts:
--------------------------------------------------------------------------------

```typescript
import isNumber from 'lodash/isNumber.js';
import isString from 'lodash/isString.js';
import { parse, stringify } from 'lossless-json';
import { BigNumber } from "bignumber.js";

/**
 * 是 undefined 或 null
 * @param value
 * @returns
 */
export function isEmpty(value: any) {
  return value === undefined || value === null;
}

/**
 * 是undefined null 空字串
 * @param input
 * @returns {boolean}
 */
export function isEmptyStr(input: any) {
  return (input === null || input === undefined || input === '');
}

export function isPreMode() {
  let pre: boolean = false;
  try {
    const env = process?.env || {};
    pre = env.NODE_ENV === 'development';
  } catch (e) {
    console.error(e);
  }
  return pre;
}

export function isVerboseMode() {
  let verbose: boolean = false;
  try {
    const env = process?.env || {};
    verbose = env.VERBOSE === 'true';
  } catch (e) {
    console.error(e);
  }
  return verbose;
}

export function getEnvRegion() {
  let regionId: string = '';
  try {
    regionId = process.env.DATAWORKS_REGION || process.env.REGION || '';
  } catch (e) {
    console.error(e);
  }
  return regionId;
}

export function getEnvInfo() {
  let envInfoStr: string = '';
  try {
    const env = process?.env || {};
    envInfoStr = toJSONString(env);
  } catch (e) {
    console.error(e);
  }
  return envInfoStr;
}

/** 检查 number 是否超过最大值,如果超过就用 BigInt */
export function getNumberString(v: number) {
  let result: number | string = v;
  try {
    if (!isNumber(v)) return result;
    if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {
      result = String(v);
    }
  } catch (e) {
    console.error(e);
  }
  return result;
}

/** 检查 number 是否超过最大值,如果超过就用 BigInt */
export function getNumber(v: number) {
  let result: number | BigInt = v;
  try {
    if (!isNumber(v)) return result;
    if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {
      result = BigInt(v);
    }
  } catch (e) {
    console.error(e);
  }
  return result;
}

/** 检查 number 是否超过最大值,如果超过就用 BigNumber (string) */
export function getBigNumber(v: number) {
  let result: number | BigNumber = v;
  try {
    if (!isNumber(v)) return result;
    if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {
      result = new BigNumber(v);
    }
  } catch (e) {
    console.error(e);
  }
  return result;
}

export function getMcpResourceName(params: { toolName?: string; }) {
  return params?.toolName || '';
}

export function isBigNumber(num: number) {
  try {
    return !Number.isSafeInteger(+num);
  } catch (e) {
    console.error(e);
    return true;
  }
};

/** 将 string 里有大于 Number.MAX_SAFE_INTEGER 的数字转换为 特别的string */
export function parseJSONForBigNumber(jsonString: string, prefix = '__big_number__') {
  let result;
  try {
    // 自定义 reviver 函数
    function bigIntReviver(key: string, value: any) {
      if (isNumber(value) && isBigNumber(value)) {
        return `${prefix}${value}`;
      }
      return value;
    }
    result = JSON.parse(jsonString, bigIntReviver);
  } catch (e) {
    console.error(e);
  }
  return result;
}

/** 将值开头为 __big_number__ 转回正常的值 */
export function stringifyJSONForBigNumber(json: any, prefix = '__big_number__') {
  let result: string = '';
  try {
    function replacer(key: string, value: any) {
      if (isString(value) && value.startsWith(prefix)) {
        return value.slice(prefix.length);
      }
      return value;
    }
    result = JSON.stringify(json, replacer);

  } catch (e) {
    console.error(e);
  }
  return result;
}

/** 处理 big number 问题 */
export function parseJSONString(jsonString: string) {
  let result: any;
  try {
    result = parse(jsonString);
  } catch (e) {
    console.error(e);
  }
  return result;
}

/** 处理 big number 问题 */
export function toJSONString(json: any, replacer?: (number | string)[] | null, space?: string | number) {
  let result: string = '';
  try {
    result = stringify(json, replacer, space) || '';
  } catch (e) {
    console.error(e);
  }
  return result;
}

```

--------------------------------------------------------------------------------
/src/mcp/index.ts:
--------------------------------------------------------------------------------

```typescript
import isString from 'lodash/isString.js';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { OpenApiClientInstance } from "../openApiClient/index.js";
import { MCPSchemaShape, zodToMCPShape } from "../utils/zodToMCPSchema.js";
import type { ActionExample, ActionTool } from "../types/action.js";
import { convertInputSchemaToSchema } from "../utils/initDataWorksTools.js";
import { ServerOptions } from "@modelcontextprotocol/sdk/server/index.js";
import { getMcpResourceName, toJSONString } from "../utils/common.js";

/**
 * Creates an MCP server from a set of actions
 */
export function createMcpServer(
  actions: Record<string, ActionTool>,
  agent: OpenApiClientInstance,
  options: {
    name: string;
    version: string;
    serverOptions?: ServerOptions;
  }
) {

  const serverOptions: ServerOptions = options?.serverOptions || {};

  // Create MCP server instance
  const serverWrapper = new McpServer({
    name: options.name,
    version: options.version,
  }, serverOptions);

  // Convert each action to an MCP tool
  for (const [key, action] of Object.entries(actions)) {

    let paramsSchema: MCPSchemaShape = {};
    if (action?.schema) {
      const { result = {} } = action?.schema ? zodToMCPShape(action.schema) : {};
      paramsSchema = result;
    } else {
      const { result = {} } = zodToMCPShape(convertInputSchemaToSchema(action?.inputSchema));
      paramsSchema = result;
    }

    console.log('Active tool', action.name);

    let actionDescription = action.description || '';

    // 如果有对应的 MCP Resource,需要放在 description 给模型提示
    if (action.hasMcpResource) {
      actionDescription += `\n*This Tool has a 'MCP Resource',please request ${getMcpResourceName({ toolName: action.name })}(MCP Resource) to get more examples for using this tool.`;
    }

    serverWrapper.tool(
      action.name,
      actionDescription,
      paramsSchema,
      async (params) => {
        try {
          // Execute the action handler with the params directly
          const result = await action?.handler?.(agent, params);

          // Format the result as MCP tool response
          return {
            content: [
              {
                type: "text",
                text: result ? (isString(result) ? result : toJSONString(result, null, 2)) : '',
              }
            ]
          };
        } catch (error) {
          console.error("error", error);
          // Handle errors in MCP format
          return {
            isError: true,
            content: [
              {
                type: "text",
                text: error instanceof Error ? error.message : "Unknown error occurred"
              }
            ]
          };
        }
      }
    );

    // Add examples as prompts if they exist
    if (action.examples && action.examples.length > 0) {
      serverWrapper.prompt(
        `${action.name}-examples`,
        {
          showIndex: z.string().optional().describe("Example index to show (number)")
        },
        (args) => {
          const showIndex = args.showIndex ? parseInt(args.showIndex) : undefined;
          const examples = action?.examples?.flat?.();
          const selectedExamples = (typeof showIndex === 'number'
            ? [examples?.[showIndex]]
            : examples) as ActionExample[];

          const exampleText = selectedExamples?.map((ex, idx) => `
Example ${idx + 1}:
Input: ${toJSONString(ex?.input, null, 2)}
Output: ${toJSONString(ex?.output, null, 2)}
Explanation: ${ex?.explanation}
            `)
            .join('\n');

          return {
            messages: [
              {
                role: "user",
                content: {
                  type: "text",
                  text: `Examples for ${action.name}:\n${exampleText}`
                }
              }
            ]
          };
        }
      );
    }
  }

  return serverWrapper;
}
/**
 * Helper to start the MCP server with stdio transport
 * 
 * @param actions - The actions to expose to the MCP server
 * @param agent - Aliyun Open API client instance
 * @param options - The options for the MCP server
 * @returns The MCP server
 * @throws Error if the MCP server fails to start
 * @example 
 * import { ACTIONS } from "./actions";
 * import { startMcpServer } from "./mcpWrapper";
 *
 * const agent =  OpenApiClient.createClient({
        REGION: process.env.REGION || "",
        ALIBABA_CLOUD_ACCESS_KEY_ID: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID || "",
        ALIBABA_CLOUD_ACCESS_KEY_SECRET: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET || "",
      });
 * 
 * startMcpServer(ACTIONS, agent, {
 *   name: "dataworks-actions",
 *   version: "1.0.0"
 * });
 */
export async function startMcpServer(
  actions: Record<string, ActionTool>,
  agent: OpenApiClientInstance,
  options: {
    name: string;
    version: string;
    serverOptions?: ServerOptions;
  }
) {
  try {
    const serverWrapper = createMcpServer(actions, agent, options);
    const transport = new StdioServerTransport();
    await serverWrapper.connect(transport);

    if (process.env.LOGGING_LEVEL) {
      serverWrapper?.server?.sendLoggingMessage?.({
        level: process.env.LOGGING_LEVEL as any,
        data: "Server started successfully",
      });
    }

    return serverWrapper;
  } catch (error) {
    console.error("Error starting MCP server", error);
    throw error;
  }
}

```

--------------------------------------------------------------------------------
/src/tools/callTool.ts:
--------------------------------------------------------------------------------

```typescript
// import DataWorksPublic20240518 from '@alicloud/dataworks-public20240518';
// import * as DataWorksPublic20240518Classes from '@alicloud/dataworks-public20240518';
import fetch from 'node-fetch';
import OpenApi from '@alicloud/openapi-client';
import OpenApiUtil from '@alicloud/openapi-util';
import Util from '@alicloud/tea-util';
import tea from '@alicloud/tea-typescript';
import record from '../utils/record.js';
import isNumber from 'lodash/isNumber.js';
import isString from 'lodash/isString.js';
import isObject from 'lodash/isObject.js';
import { OpenApiClientInstance } from "../openApiClient/index.js";
import { IAlibabaCloudOpenApiJsonResponse, OpenApiConfigs } from '../types/alibabaCloudApi.js';
import { ActionTool } from '../types/action.js';
import { isEmptyStr, isVerboseMode, getEnvInfo, toJSONString, parseJSONString, isBigNumber } from '../utils/common.js';

/**
 * Get detailed and latest information about any topic using Perplexity AI.
 * @param agent Aliyun Open API instance
 * @param prompt Text description of the topic to get information about
 * @returns Object containing the generated information
 */
async function callTool(
  agent: OpenApiClientInstance,
  apiKey: string,
  actionTool: ActionTool,
  input?: Record<string, any>,
) {

  let apiRequestConfigs: OpenApi.Params = {} as any;
  let query: any = {};
  let body: any = {};

  // API版本号
  const version = actionTool?.annotations?.version;

  try {

    // 原来使用特定调用的方式
    // import * as DataWorksPublic20240518Classes from '@alicloud/dataworks-public20240518';
    // const FunctionClass = (DataWorksPublic20240518Classes as any)[`${apiKey}Request`];
    // const request = Reflect.construct(FunctionClass, input as any);
    // const runtime = new Util.RuntimeOptions({});
    // // 把apiKey第一个大小改小写
    // const funcName = `${apiKey.charAt(0).toLowerCase()}${apiKey.slice(1) || ''}WithOptions`;
    // return await (agent as any)[funcName](request, runtime);

    // 使用泛化方式调用
    // https://help.aliyun.com/zh/sdk/developer-reference/generalized-call-node-js

    // path 为空就是 RPC
    const style = isEmptyStr(actionTool?.annotations?.path) ? 'RPC' : 'ROA';

    const method = actionTool?.annotations?.method;

    let hasInQueryParams = false;
    let hasInBodyParams = false;
    let hasInByteParams = false;
    let hasInFormDataParams = false;

    // 需要重新 assign 下
    const _input: any = { ...input };

    Object.keys(_input)?.forEach((key) => {
      let value = _input[key];

      // if (isNumber(value)) {
      //   // 查看值有没有溢出,如果溢出了就用string
      //   if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) {
      //     value = String(value);
      //   }
      // }

      const paramMeta = actionTool?.annotations?.pmd?.[key];
      if (paramMeta?.in === 'body') {
        hasInBodyParams = true;
        body[key] = value;
      } else if (paramMeta?.in === 'formData') {
        hasInFormDataParams = true;
        if (paramMeta?.style === 'json') {
          body[key] = toJSONString(value);
        } else {
          body[key] = value;
        }
      } else if (paramMeta?.in === 'byte') {
        hasInByteParams = true;
        body[key] = value;
      } else if (paramMeta?.in === 'query') {
        hasInQueryParams = true;
        if (paramMeta?.style === 'json') {
          query[key] = toJSONString(value);
        } else {
          query[key] = value;
        }
      } else {
        query[key] = value;
      }
    });

    const reqBodyType = hasInByteParams ? 'byte' : hasInFormDataParams ? 'formData' : 'json';

    const bodyJson = toJSONString(body);
    if (reqBodyType === 'byte') {
      body = Buffer.from(bodyJson);
    } else if (reqBodyType === 'json') {
      body = bodyJson;
    }

    const configs: OpenApiConfigs = {
      style, // API风格
      action: apiKey, // API 名称
      version,
      protocol: 'HTTPS', // API协议
      method, // 请求方法
      authType: 'AK',
      pathname: `/`, // 接口 PATH
      reqBodyType, // 接口请求体内容格式
      bodyType: 'string', // 使用 string,如果使用 json 在 sdk 里会有精度丢失的问题
    };

    if (['GET', 'DELETE'].includes(method || '')) delete configs.reqBodyType;

    apiRequestConfigs = new OpenApi.Params(configs);

    // GET DELETE 这边需要把 body 设定为空,不然签名不会过
    if (['GET', 'DELETE'].includes(method || '')) body = null;

    const request = new OpenApi.OpenApiRequest({ query, body });
    // runtime default settings https://github.com/aliyun/tea-util/blob/5f4bdebef3b57d33207b6bc44af6ed5e1a009959/ts/test/client.spec.ts#L133
    const runtime = new Util.RuntimeOptions({
      readTimeout: process.env.OPEN_API_READ_TIMEOUT ? Number(process.env.OPEN_API_READ_TIMEOUT) : 10000,
      connectTimeout: process.env.OPEN_API_CONNECT_TIMEOUT ? Number(process.env.OPEN_API_CONNECT_TIMEOUT) : 10000,
    });
    // 查看 https://github.com/aliyun/darabonba-openapi/blob/master/ts/src/client.ts
    const res = await (agent as any)?.callApi?.(apiRequestConfigs, request, runtime);
    let result: IAlibabaCloudOpenApiJsonResponse['body'] | null = null;
    try {
      // 当执行成功,只取 message.body 的信息
      const obj = res?.statusCode === 200 && res?.body ? res?.body : res;
      result = parseJSONString(obj);
      await record({ success: true, toolName: apiKey, requestId: result?.RequestId, version });
    } catch (e) {
      console.error(e);
    }

    return result;

  } catch (error: any) {
    const verbose = isVerboseMode();
    const errorMsg = `Call tool failed: ${error.message}, api key: ${apiKey}, api request configs: ${toJSONString(apiRequestConfigs)}, query: ${toJSONString(query)}, body: ${toJSONString(body)}${verbose ? `, env info: ${getEnvInfo()}` : ''}`;
    const recordErr = `Call tool failed: ${error.message}, api key: ${apiKey}, api request configs: ${toJSONString(apiRequestConfigs)}, query: ${toJSONString(query)}, body: ${toJSONString(body)}`;
    await record({ success: false, toolName: apiKey, version, error: recordErr });
    throw new Error(errorMsg);
  }
};

export default callTool;

```

--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------

```
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

```