#
tokens: 21027/50000 22/22 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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:
--------------------------------------------------------------------------------

```
1 | node_modules
2 | build
3 | 
```

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

```markdown
  1 | [![MseeP.ai Security Assessment Badge](https://mseep.net/mseep-audited.png)](https://mseep.ai/app/aliyun-alibabacloud-dataworks-mcp-server)
  2 | 
  3 | # DataWorks MCP Server
  4 | 
  5 | 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.
  6 | 
  7 | ## Overview
  8 | 
  9 | This MCP server:
 10 | 
 11 | * Interact with DataWorks Open API
 12 | * Manage DataWorks resources
 13 | 
 14 | The server implements the Model Context Protocol specification to standardize cloud resource interactions for AI agents.
 15 | 
 16 | ## Prerequisites
 17 | 
 18 | * Node.js (v16 or higher)
 19 | * pnpm (recommended), npm, or yarn
 20 | * DataWorks Open API with access key and secret key
 21 | 
 22 | ## Installation
 23 | 
 24 | ### Option 1: Install from npm (recommend for clients like Cursor/Cline)
 25 | 
 26 | ```bash
 27 | # Install globally
 28 | npm install -g alibabacloud-dataworks-mcp-server
 29 | 
 30 | # Or install locally in your project
 31 | npm install alibabacloud-dataworks-mcp-server
 32 | ```
 33 | 
 34 | ### Option 2: Build from Source (for developers)
 35 | 
 36 | 1. Clone this repository:
 37 | ```bash
 38 | git clone https://github.com/aliyun/alibabacloud-dataworks-mcp-server
 39 | cd alibabacloud-dataworks-mcp-server
 40 | ```
 41 | 
 42 | 2. Install dependencies (pnpm is recommended, npm is supported):
 43 | ```bash
 44 | pnpm install
 45 | ```
 46 | 
 47 | 3. Build the project:
 48 | ```bash
 49 | pnpm run build
 50 | ```
 51 | 
 52 | 4. Development the project (by @modelcontextprotocol/inspector):
 53 | ```bash
 54 | pnpm run dev
 55 | ```
 56 | open http://localhost:5173
 57 | 
 58 | ## Configuration
 59 | 
 60 | ### MCP Server Configuration
 61 | 
 62 | If you installed via npm (Option 1):
 63 | ```json
 64 | {
 65 |   "mcpServers": {
 66 |     "alibabacloud-dataworks-mcp-server": {
 67 |       "command": "npx",
 68 |       "args": ["alibabacloud-dataworks-mcp-server"],
 69 |       "env": {
 70 |         "REGION": "your_dataworks_open_api_region_id_here",
 71 |         "ALIBABA_CLOUD_ACCESS_KEY_ID": "your_alibaba_cloud_access_key_id",
 72 |         "ALIBABA_CLOUD_ACCESS_KEY_SECRET": "your_alibaba_cloud_access_key_secret",
 73 |         "TOOL_CATEGORIES": "optional_your_tool_categories_here_ex_UTILS",
 74 |         "TOOL_NAMES": "optional_your_tool_names_here_ex_ListProjects"
 75 |       },
 76 |       "disabled": false,
 77 |       "autoApprove": []
 78 |     }
 79 |   }
 80 | }
 81 | ```
 82 | 
 83 | If you built from source (Option 2):
 84 | ```json
 85 | {
 86 |   "mcpServers": {
 87 |     "alibabacloud-dataworks-mcp-server": {
 88 |       "command": "node",
 89 |       "args": ["/path/to/alibabacloud-dataworks-mcp-server/build/index.js"],
 90 |       "env": {
 91 |         "REGION": "your_dataworks_open_api_region_id_here",
 92 |         "ALIBABA_CLOUD_ACCESS_KEY_ID": "your_alibaba_cloud_access_key_id",
 93 |         "ALIBABA_CLOUD_ACCESS_KEY_SECRET": "your_alibaba_cloud_access_key_secret",
 94 |         "TOOL_CATEGORIES": "optional_your_tool_categories_here_ex_SERVER_IDE_DEFAULT",
 95 |         "TOOL_NAMES": "optional_your_tool_names_here_ex_ListProjects"
 96 |       },
 97 |       "disabled": false,
 98 |       "autoApprove": []
 99 |     }
100 |   }
101 | }
102 | ```
103 | 
104 | ### Environment Setup
105 | 
106 | init variables in your environment:
107 | 
108 | ```env
109 | # DataWorks Configuration
110 | REGION=your_dataworks_open_api_region_id_here
111 | ALIBABA_CLOUD_ACCESS_KEY_ID=your_alibaba_cloud_access_key_id
112 | ALIBABA_CLOUD_ACCESS_KEY_SECRET=your_alibaba_cloud_access_key_secret
113 | TOOL_CATEGORIES=optional_your_tool_categories_here_ex_SERVER_IDE_DEFAULT
114 | TOOL_NAMES=optional_your_tool_names_here_ex_ListProjects
115 | ```
116 | 
117 | ### Configuration Description
118 | - Use Guide Description [Link](https://www.alibabacloud.com/help/dataworks/user-guide/dataworks-mcp-server-function-usage#1ecf2a04b5ilh)
119 | 
120 | ## Project Structure
121 | 
122 | ```
123 | alibabacloud-dataworks-mcp-server/
124 | ├── src/
125 | │   ├── index.ts          # Main entry point
126 | ├── package.json
127 | └── tsconfig.json
128 | ```
129 | 
130 | ## Available Tools
131 | 
132 | The MCP server provides the following DataWorks tools:
133 | 
134 | See this [link](https://dataworks.data.aliyun.com/dw-pop-mcptools)
135 | 
136 | ## Security Considerations
137 | 
138 | * Keep your private key secure and never share it
139 | * Use environment variables for sensitive information
140 | * Regularly monitor and audit AI agent activities
141 | 
142 | ## Troubleshooting
143 | 
144 | If you encounter issues:
145 | 
146 | 1. Verify your Aliyun Open API access key and secret key are correct
147 | 2. Check your region id is correct
148 | 3. Ensure you're on the intended network (mainnet, testnet, or devnet)
149 | 4. Verify the build was successful
150 | 
151 | ## Dependencies
152 | 
153 | Key dependencies include:
154 | * [@alicloud/dataworks-public20240518](https://github.com/alibabacloud-sdk-swift/dataworks-public-20240518)
155 | * [@alicloud/openapi-client](https://github.com/aliyun/darabonba-openapi)
156 | 
157 | ## Contributing
158 | 
159 | Contributions are welcome! Please feel free to submit a Pull Request.
160 | 
161 | 1. Fork the repository
162 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
163 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
164 | 4. Push to the branch (`git push origin feature/amazing-feature`)
165 | 5. Open a Pull Request
166 | 
167 | ## License
168 | 
169 | This project is licensed under the Apache 2.0 License.
170 | 
```

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

```typescript
 1 | export interface Resource {
 2 |   uri?: string;
 3 |   name?: string;
 4 |   description?: string;
 5 |   mimeType?: 'text/json' | 'text/plain' | 'image/png';
 6 | }
 7 | 
 8 | /** mcp 接口返回 */
 9 | export interface DataWorksMCPResponse {
10 |   jsonrpc: string;
11 |   id: string;
12 |   result: {
13 |     /** resource 白名单 */
14 |     a2reslist: string[];
15 |   };
16 | }
17 | 
```

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

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": [
14 |     "src/**/*"
15 |   ],
16 |   "exclude": [
17 |     "node_modules"
18 |   ]
19 | }
```

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

```typescript
 1 | import dayjs from 'dayjs';
 2 | import { OpenApiClientInstance } from "../../openApiClient/index.js";
 3 | 
 4 | export default async function toTimestamps(
 5 |   agent: OpenApiClientInstance,
 6 |   dateTimeDisplay?: string[],
 7 | ) {
 8 |   try {
 9 |     const result: number[] = [];
10 |     if (dateTimeDisplay) {
11 |       dateTimeDisplay?.forEach?.((str) => {
12 |         try {
13 |           const timestamp = dayjs(str).valueOf();
14 |           result.push(timestamp);
15 |         } catch (e) {
16 |           console.error(e);
17 |         }
18 |       });
19 |     }
20 |     return result;
21 |   } catch (error: any) {
22 |     throw new Error(`To timestamps failed: ${error.message}`);
23 |   }
24 | }
25 | 
```

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

```typescript
 1 | import dayjs from 'dayjs';
 2 | import { OpenApiClientInstance } from "../../openApiClient/index.js";
 3 | 
 4 | export default async function convertTimestamps(
 5 |   agent: OpenApiClientInstance,
 6 |   timestamps?: number[],
 7 |   format?: string,
 8 | ) {
 9 |   try {
10 |     const result: string[] = [];
11 |     if (timestamps) {
12 |       if (format) {
13 |         timestamps?.forEach?.((timestamp) => {
14 |           try {
15 |             const date = new Date(timestamp);
16 |             const display = dayjs(date).format(format || 'YYYY-MM-DD');
17 |             result.push(display);
18 |           } catch (e) {
19 |             console.error(e);
20 |           }
21 |         });
22 |       };
23 |     }
24 |     return result;
25 |   } catch (error: any) {
26 |     throw new Error(`Convert timestamps failed: ${error.message}`);
27 |   }
28 | }
29 | 
```

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

```typescript
 1 | import fetch from 'node-fetch';
 2 | import { dataWorksRecordUrl } from '../constants/index.js';
 3 | 
 4 | /** 记录是否失败 */
 5 | export default async function record(options: {
 6 |   success?: boolean;
 7 |   error?: string;
 8 |   toolName?: string;
 9 |   resourceUri?: string;
10 |   version?: string;
11 |   requestId?: string;
12 | } = {}) {
13 |   try {
14 |     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`);
15 |     console.debug('Success record');
16 |   } catch (e) {
17 |     console.error('Failed to record:', e);
18 |   }
19 | }
```

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

```typescript
 1 | import fs from 'fs';
 2 | import fetch from 'node-fetch';
 3 | import { isPreMode, parseJSONString } from "./common.js";
 4 | import { dataWorksMcpUrl, dataWorksPreMcpUrl } from '../constants/index.js';
 5 | import { DataWorksMCPResponse } from '../types/common.js';
 6 | 
 7 | /** 获取 dw mcp 接口 */
 8 | export default async function getDataWorksMcp(options?: {}) {
 9 |   const isPre = isPreMode();
10 | 
11 |   // 如果是预发环境,支持本地文件
12 |   const fileUri = process.env.MCP_FILE_URI || (isPre ? dataWorksPreMcpUrl : dataWorksMcpUrl);
13 | 
14 |   let dwMcpRes;
15 |   try {
16 |     if (!fileUri?.startsWith?.('http')) {
17 |       // local file
18 |       const fileContent = fs.readFileSync(fileUri, 'utf8');
19 |       dwMcpRes = parseJSONString(fileContent);
20 |     } else {
21 |       // http file
22 |       const queryRes = await fetch(fileUri);
23 |       const resStr = await queryRes.text() as string;
24 |       dwMcpRes = parseJSONString(resStr) as DataWorksMCPResponse;
25 |     }
26 |   } catch (e) {
27 |     console.error('Failed to get getDataWorksMcp:', e);
28 |   }
29 |   return dwMcpRes;
30 | }
```

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

```typescript
 1 | import OpenApiClientInstance from "../openApiClient/index.js";
 2 | import callTool from "../tools/callTool.js";
 3 | import { ActionTool } from "../types/action.js";
 4 | 
 5 | export const getHandler = (apiKey: string, actionTool: ActionTool) => async (agent: OpenApiClientInstance, input: Record<string, any>) => {
 6 |   try {
 7 | 
 8 |     const response = await callTool(agent, apiKey, actionTool, input);
 9 |     return response;
10 | 
11 |   } catch (error: any) {
12 |     // Handle specific Perplexity API error types
13 |     if (error.response) {
14 |       const { status, data } = error.response;
15 |       if (status === 429) {
16 |         return {
17 |           statusCode: status,
18 |           body: "Error: Rate limit exceeded. Please try again later.",
19 |         };
20 |       }
21 |       return {
22 |         statusCode: status,
23 |         body: `Error: ${data.error?.message || error.message}`,
24 |       };
25 |     }
26 | 
27 |     return {
28 |       body: `Failed to get information: ${error.message}`,
29 |     };
30 |   }
31 | };
32 | 
33 | export type { ActionTool, ActionExample, Handler } from "../types/action.js";
34 | 
```

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

```typescript
 1 | 
 2 | import { z } from "zod";
 3 | 
 4 | // Define the raw shape type that MCP tools expect
 5 | export type MCPSchemaShape = {
 6 |   [key: string]: z.ZodType<any>;
 7 | };
 8 | 
 9 | // Type guards for Zod schema types
10 | function isZodOptional(schema: z.ZodTypeAny): schema is z.ZodOptional<any> {
11 |   return schema instanceof z.ZodOptional;
12 | }
13 | 
14 | function isZodObject(schema: z.ZodTypeAny): schema is z.ZodObject<any> {
15 |   // Check both instanceof and the typeName property
16 |   return (
17 |     schema instanceof z.ZodObject ||
18 |     (schema?._def?.typeName === 'ZodObject')
19 |   );
20 | }
21 | 
22 | /**
23 |  * Converts a Zod object schema to a flat shape for MCP tools
24 |  * @param schema The Zod schema to convert
25 |  * @returns A flattened schema shape compatible with MCP tools
26 |  * @throws Error if the schema is not an object type
27 |  */
28 | export function zodToMCPShape(schema: z.ZodTypeAny): { result: MCPSchemaShape, keys: string[] } {
29 |   if (!isZodObject(schema)) {
30 |     throw new Error("MCP tools require an object schema at the top level");
31 |   }
32 | 
33 |   const shape = schema.shape;
34 |   const result: MCPSchemaShape = {};
35 | 
36 |   for (const [key, value] of Object.entries(shape)) {
37 |     result[key] = isZodOptional(value as any) ? (value as any).unwrap() : value;
38 |   }
39 | 
40 |   return {
41 |     result,
42 |     keys: Object.keys(result)
43 |   };
44 | }
45 | 
```

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

```typescript
 1 | import { OpenApiClientInstance } from "../openApiClient/index.js";
 2 | import { z } from "zod";
 3 | import { ApiMethodUpperCase, ApiParameter, ApiParameterSchema } from "./alibabaCloudApi.js";
 4 | 
 5 | /**
 6 |  * Example of an action with input and output
 7 |  */
 8 | export interface ActionExample {
 9 |   input: Record<string, any>;
10 |   output: Record<string, any>;
11 |   explanation: string;
12 | }
13 | 
14 | /**
15 |  * Handler function type for executing the action
16 |  */
17 | export type Handler = (
18 |   agent: OpenApiClientInstance,
19 |   input: Record<string, any>,
20 | ) => Promise<Record<string, any>>;
21 | 
22 | export interface DwInputSchema extends Omit<ApiParameterSchema, 'required' | 'properties' | 'items'> {
23 |   /** 有 properties 时,required 为 string[] */
24 |   required?: string[];
25 |   items?: DwInputSchema;
26 |   properties?: { [name: string]: DwInputSchema };
27 | }
28 | 
29 | /**
30 |  * Cline 的市集应用在 Tool 上包了一个 Action,做进一步扩展
31 |  * Main Action interface inspired by ELIZA
32 |  * This interface makes it easier to implement actions across different frameworks
33 |  */
34 | export interface ActionTool {
35 |   /**
36 |    * Unique name of the action
37 |    */
38 |   name: string;
39 | 
40 |   /**
41 |    * Detailed description of what the action does
42 |    */
43 |   description?: string;
44 | 
45 |   /**
46 |    * 直接写好 schema。
47 |    * https://modelcontextprotocol.io/docs/concepts/tools
48 |    */
49 |   inputSchema?: DwInputSchema;
50 | 
51 |   annotations?: {
52 |     path?: string;
53 |     method?: ApiMethodUpperCase;
54 |     /** ex 2024-05-18 */
55 |     version?: string;
56 |     example?: string;
57 |     category?: string;
58 |     pmd: {
59 |       [name: string]: ApiParameter;
60 |     };
61 |   };
62 | 
63 |   // --------------- 以下为扩展 -----------------
64 | 
65 |   /**
66 |    * Alternative names/phrases that can trigger this action
67 |    */
68 |   similes?: string[];
69 | 
70 |   /**
71 |    * Array of example inputs and outputs for the action
72 |    * Each inner array represents a group of related examples
73 |    */
74 |   examples?: ActionExample[][];
75 | 
76 |   /**
77 |    * Zod schema for input validation
78 |    */
79 |   schema?: z.ZodType<any>;
80 | 
81 |   /**
82 |    * Function that executes the action
83 |    */
84 |   handler?: Handler;
85 | 
86 |   /**
87 |    * 有对应的 MCP Resource
88 |    */
89 |   hasMcpResource?: boolean;
90 | }
91 | 
```

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

```typescript
 1 | import fs from 'fs';
 2 | import fetch from 'node-fetch';
 3 | import { ActionTool } from "../types/action.js";
 4 | import { isPreMode, parseJSONString } from "./common.js";
 5 | import { dataWorksPopMcpToolsUrl, dataWorksPrePopMcpToolsUrl } from '../constants/index.js';
 6 | 
 7 | export default async function getDataWorksPopMcpTools(options?: { categories?: string[]; names?: string[]; }) {
 8 |   const isPre = isPreMode();
 9 |   // 如果是预发环境,支持本地文件
10 |   const toolFileUri = process.env.TOOL_FILE_URI || (isPre ? dataWorksPrePopMcpToolsUrl : dataWorksPopMcpToolsUrl);
11 | 
12 |   let dataWorksPopMcpTools: ActionTool[] = [];
13 |   try {
14 |     if (!toolFileUri?.startsWith?.('http')) {
15 |       // local file
16 |       const fileContent = fs.readFileSync(toolFileUri, 'utf8');
17 |       dataWorksPopMcpTools = parseJSONString(fileContent);
18 | 
19 |       // 如果有传入 categories 只挑有列的
20 |       const categories = (options?.categories || process.env?.TOOL_CATEGORIES?.split?.(',')) || [];
21 |       if (categories?.length) {
22 |         dataWorksPopMcpTools = dataWorksPopMcpTools.filter((item) => {
23 |           return categories?.includes(item?.annotations?.category || '');
24 |         });
25 |       }
26 | 
27 |       // 如果有传入 names 只挑有列的
28 |       const names = (options?.names || process.env?.TOOL_NAMES?.split?.(',')) || [];
29 |       if (names?.length) {
30 |         dataWorksPopMcpTools = dataWorksPopMcpTools.filter((item) => {
31 |           return names?.includes(item?.name || '');
32 |         });
33 |       }
34 | 
35 |     } else {
36 |       // http file
37 | 
38 |       // 接口过滤
39 |       const categories = (options?.categories?.join?.(',') || process.env?.TOOL_CATEGORIES) || '';
40 |       const names = (options?.names?.join?.(',') || process.env?.TOOL_NAMES) || '';
41 |       let _params = '';
42 |       if (categories) _params += `categories=${encodeURIComponent(categories)}`;
43 |       if (names) _params += `${_params ? '&' : ''}names=${encodeURIComponent(names)}`;
44 |       if (_params) _params = `?${_params}`;
45 |       const queryRes = await fetch(`${toolFileUri}${_params}`);
46 |       const queryResStr = await queryRes.text() as string;
47 |       dataWorksPopMcpTools = parseJSONString(queryResStr) as ActionTool[];
48 |     }
49 |   } catch (e) {
50 |     console.error('Failed to get dataWorksPopMcpTools:', e);
51 |   }
52 |   return dataWorksPopMcpTools;
53 | }
```

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

```json
 1 | {
 2 |   "name": "alibabacloud-dataworks-mcp-server",
 3 |   "version": "1.0.43",
 4 |   "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.",
 5 |   "main": "build/index.js",
 6 |   "module": "build/index.js",
 7 |   "type": "module",
 8 |   "icon": "icon.png",
 9 |   "bin": {
10 |     "alibabacloud-dataworks-mcp-server": "./build/index.js"
11 |   },
12 |   "scripts": {
13 |     "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
14 |     "start": "pnpm run build; REGION=cn-shanghai node build/index.js",
15 |     "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",
16 |     "restart": "pnpm run build; REGION=cn-shanghai node build/index.js",
17 |     "pre-start": "pnpm run build; REGION=cn-shanghai NODE_ENV=development VERBOSE=true node build/index.js"
18 |   },
19 |   "files": [
20 |     "build"
21 |   ],
22 |   "repository": {
23 |     "type": "git",
24 |     "url": "[email protected]:aliyun/alibabacloud-dataworks-mcp-server.git"
25 |   },
26 |   "keywords": [
27 |     "alibaba",
28 |     "aliyun",
29 |     "cloud",
30 |     "computing",
31 |     "dataworks",
32 |     "big data",
33 |     "mcp",
34 |     "ai",
35 |     "open api",
36 |     "dataworks-mcp"
37 |   ],
38 |   "author": "DataWorks",
39 |   "license": "Apache-2.0",
40 |   "dependencies": {
41 |     "@alicloud/credentials": "2.4.2",
42 |     "@alicloud/dataworks-public20240518": "^6.0.2",
43 |     "@alicloud/openapi-client": "^0.4.13",
44 |     "@alicloud/openapi-util": "^0.3.2",
45 |     "@alicloud/tea-typescript": "^1.8.0",
46 |     "@alicloud/tea-util": "^1.4.10",
47 |     "@modelcontextprotocol/sdk": "^1.5.0",
48 |     "@modelcontextprotocol/server-filesystem": "2025.3.28",
49 |     "bignumber.js": "^9.1.0",
50 |     "dayjs": "^1.11.13",
51 |     "dotenv": "^16.4.7",
52 |     "express": "4.20.0",
53 |     "lodash": "^4.17.21",
54 |     "lossless-json": "^4.0.2",
55 |     "node-fetch": "3.3.2",
56 |     "openai": "^4.77.0",
57 |     "path": "^0.12.7",
58 |     "uuid": "9.0.1",
59 |     "zod": "^3.24.2"
60 |   },
61 |   "devDependencies": {
62 |     "@anthropic-ai/sdk": "^0.39.0",
63 |     "@modelcontextprotocol/inspector": "^0.6.0",
64 |     "@types/bignumber.js": "^5.0.4",
65 |     "@types/lodash": "^4.17.16",
66 |     "@types/node": "^22.13.4",
67 |     "tailwindcss": "^3.0.0",
68 |     "typescript": "^5.7.3"
69 |   },
70 |   "resolutions": {
71 |     "@alicloud/credentials": "2.4.2",
72 |     "uuid": "9.0.1"
73 |   },
74 |   "packageManager": "[email protected]",
75 |   "publishConfig": {
76 |     "registry": "https://registry.npmjs.org"
77 |   },
78 |   "homepage": "https://github.com/aliyun/alibabacloud-dataworks-mcp-server.git",
79 |   "bugs": {
80 |     "url": "https://github.com/aliyun/alibabacloud-dataworks-mcp-server/issues",
81 |     "mail": ""
82 |   },
83 |   "tnpm": {
84 |     "lockfile": "enable",
85 |     "mode": "npm"
86 |   }
87 | }
```

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

```typescript
 1 | import record from '../utils/record.js';
 2 | import { ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
 3 | import { isVerboseMode, getEnvInfo, getMcpResourceName, toJSONString } from '../utils/common.js';
 4 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
 5 | import { ActionTool } from '../types/action.js';
 6 | import { DataWorksMCPResponse } from '../types/common.js';
 7 | 
 8 | /**
 9 |  * init resources of this MCP server
10 |  */
11 | async function initResources(
12 |   server: McpServer['server'],
13 |   dataWorksPopMcpTools: ActionTool[],
14 |   dataworksMcpRes?: DataWorksMCPResponse,
15 | ) {
16 | 
17 |   try {
18 | 
19 |     // Resource file
20 | 
21 |     // DW 资源的白名单
22 |     const dwWhiteList: string[] = dataworksMcpRes?.result?.a2reslist || [];
23 | 
24 |     server?.setRequestHandler(ListResourcesRequestSchema, async () => {
25 |       const resourceList = dataWorksPopMcpTools?.filter((item) => dwWhiteList?.includes?.(item?.name))?.map?.((item) => {
26 |         return {
27 |           uri: item?.name,
28 |           name: getMcpResourceName({ toolName: item?.name }),
29 |           description: `${item?.name}的定义详情,如接口返回范例描述,输入参数范例等`,
30 |           mimeType: "text/json",
31 |         }
32 |       }) || [];
33 |       return {
34 |         resources: resourceList,
35 |       };
36 |     });
37 | 
38 |     server?.setRequestHandler?.(ReadResourceRequestSchema, async (request) => {
39 |       const uri = request?.params?.uri;
40 |       try {
41 |         if (uri?.startsWith?.('http')) {
42 |           const res = await fetch(uri);
43 |           const jsonStr = await res.text() || '{}'; // 不使用 .json(),可能会丢失精度
44 | 
45 |           await record({ success: true, resourceUri: uri });
46 | 
47 |           return {
48 |             contents: [
49 |               {
50 |                 uri,
51 |                 mimeType: "text/json",
52 |                 text: jsonStr,
53 |               }
54 |             ]
55 |           };
56 |         } else {
57 | 
58 |           const toolInfo = dataWorksPopMcpTools?.find?.((item) => {
59 |             return item?.name === uri;
60 |           });
61 | 
62 |           if (toolInfo) {
63 | 
64 |             await record({ success: true, resourceUri: uri });
65 | 
66 |             return {
67 |               contents: [
68 |                 {
69 |                   uri,
70 |                   mimeType: "text/json",
71 |                   text: toJSONString(toolInfo || {}),
72 |                 }
73 |               ]
74 |             };
75 |           } else {
76 |             throw new Error(`Resource not found. ${uri}`);
77 |           }
78 | 
79 |         }
80 |       } catch (e: any) {
81 |         console.error(e);
82 |         await record({ success: false, error: e?.message });
83 |         throw new Error(`Resource not found. ${uri}`);
84 |       }
85 |     });
86 | 
87 |   } catch (error: any) {
88 |     const verbose = isVerboseMode();
89 |     const errorMessage = `init resources failed: ${error.message}, ${verbose ? `, env info: ${getEnvInfo()}` : ''}`;
90 |     throw new Error(errorMessage);
91 |   }
92 | };
93 | 
94 | export default initResources;
95 | 
```

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

```typescript
  1 | 
  2 | /** https://api.aliyun.com/openmeta/struct/ApiDocs */
  3 | 
  4 | 
  5 | export type ApiParameterType = 'integer' | 'string' | 'date' | 'boolean' | 'array' | 'object';
  6 | export type ApiParameterFormat = 'int32' | 'int64';
  7 | 
  8 | interface ApiDirectory {
  9 |   id: number;
 10 |   title: string;
 11 |   type: 'directory';
 12 |   children: (string | ApiDirectory)[];
 13 | }
 14 | 
 15 | interface ApiProperty {
 16 |   id: number;
 17 |   title: string;
 18 |   type: 'directory';
 19 |   format: 'int64';
 20 |   example: string;
 21 | }
 22 | 
 23 | interface ApiSchema {
 24 |   title: string;
 25 |   description?: string;
 26 |   type: 'object';
 27 |   properties?: { [name: string]: ApiProperty };
 28 | }
 29 | 
 30 | export type ApiMethod = 'get' | 'post' | 'delete' | 'put';
 31 | export type ApiMethodUpperCase = 'GET' | 'POST' | 'DELETE' | 'PUT';
 32 | type ApiScheme = 'http' | 'https';
 33 | type ApiSecurity = {
 34 |   AK: []
 35 | };
 36 | 
 37 | export interface ApiParameterSchema {
 38 |   description?: string;
 39 |   type?: ApiParameterType;
 40 |   items?: ApiParameterSchema;
 41 |   properties?: { [name: string]: ApiParameterSchema };
 42 |   format?: ApiParameterFormat;
 43 |   required?: boolean;
 44 |   example?: string;
 45 | }
 46 | 
 47 | export interface ApiParameter {
 48 |   name?: string;
 49 |   /** https://help.aliyun.com/zh/sdk/developer-reference/generalized-call-node-js */
 50 |   in: 'query' | 'body' | 'formData' | 'byte';
 51 |   /** 【style=repeatList】时,数组的序列化方式为XXX.N的形式,例如:Instance.1=i-instance1&Instance.2=i-instance2,  需要配置元素最小值,最大值,根据需要开启repeatList参数校验,连续性校验 */
 52 |   style: 'json' | 'repeatList';
 53 |   schema?: ApiParameterSchema;
 54 | }
 55 | 
 56 | interface ApiResponseSchemaProperty {
 57 |   title: string;
 58 |   description: string;
 59 |   type: 'integer';
 60 |   format: 'int64';
 61 |   example: string;
 62 | }
 63 | 
 64 | interface ApiResponseSchema {
 65 |   title: string;
 66 |   description: string;
 67 |   type: 'object';
 68 |   properties: { [name: string]: ApiResponseSchemaProperty };
 69 | }
 70 | 
 71 | interface ApiResponse {
 72 |   schema: ApiResponseSchema;
 73 | }
 74 | 
 75 | export interface ApiObj {
 76 |   title: string;
 77 |   description?: string;
 78 |   summary: string;
 79 |   methods: ApiMethod[];
 80 |   schemes: ApiScheme[];
 81 |   security: ApiSecurity[];
 82 |   deprecated: boolean;
 83 |   systemTags: { [name: string]: any };
 84 |   parameters: ApiParameter[];
 85 |   responses: { [code: string]: ApiResponse };
 86 |   staticInfo: { [name: string]: any };
 87 |   responseDemo?: string;
 88 | }
 89 | 
 90 | interface ApiComponent {
 91 |   schemas: { [name: string]: ApiSchema };
 92 |   title: string;
 93 |   type: 'directory';
 94 | }
 95 | 
 96 | interface ApiEndpoint {
 97 |   regionId: string;
 98 |   endpoint: string;
 99 | }
100 | 
101 | export interface AlibabaCloudOpenApiInterface {
102 |   version?: string;
103 |   info?: {
104 |     style: 'RPC';
105 |     product: 'dataworks-public';
106 |     version: string;
107 |   };
108 |   directories?: ApiDirectory[];
109 |   /** 数据结构等信息 */
110 |   components?: ApiComponent[];
111 |   apis?: { [name: string]: ApiObj };
112 |   endpoints?: ApiEndpoint[];
113 | }
114 | 
115 | export interface IAlibabaCloudOpenApiJsonResponse {
116 |   statusCode?: number;
117 |   headers?: { [name: string]: string };
118 |   body?: any;
119 | }
120 | 
121 | export interface OpenApiConfigs {
122 |   style: 'ROA' | 'RPC'; // API风格
123 |   action: string; // API 名称
124 |   version?: string; // API版本号
125 |   protocol: 'HTTPS' | 'HTTP'; // API协议
126 |   method?: ApiMethodUpperCase;// 请求方法
127 |   authType: 'AK';
128 |   pathname: string; // 接口 PATH
129 |   reqBodyType?: 'formData' | 'byte' | 'json';// 接口请求体内容格式
130 |   bodyType: 'binary' | 'array' | 'string' | 'json' | 'byte'; // 接口响应体内容格式
131 | }
```

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

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import * as dotenv from "dotenv";
  4 | import OpenApiClient from "./openApiClient/index.js";
  5 | import initDataWorksTools from "./utils/initDataWorksTools.js";
  6 | import initExtraTools from "./utils/initExtraTools.js";
  7 | import initResources from "./resources/initResources.js";
  8 | import getDataWorksMcp from "./utils/getDataWorksMcp.js";
  9 | import getDataWorksPopMcpTools from "./utils/getDataWorksPopMcpTools.js";
 10 | import { startMcpServer } from "./mcp/index.js";
 11 | import { mcpServerVersion } from './constants/index.js';
 12 | import { ActionTool } from "./types/action.js";
 13 | import { ServerOptions } from "@modelcontextprotocol/sdk/server/index.js";
 14 | 
 15 | dotenv.config();
 16 | 
 17 | // Validate required environment variables
 18 | function validateEnvironment() {
 19 |   const requiredEnvVars = {};
 20 | 
 21 |   const missingVars = Object.entries(requiredEnvVars)
 22 |     .filter(([_, value]) => !value)
 23 |     .map(([key]) => key);
 24 | 
 25 |   if (missingVars.length > 0) {
 26 |     throw new Error(`Missing required environment variables: ${missingVars.join(', ')}`);
 27 |   }
 28 | }
 29 | 
 30 | async function main() {
 31 |   try {
 32 |     // Validate environment before proceeding
 33 |     validateEnvironment();
 34 | 
 35 |     // Initialize the agent with error handling
 36 |     const agent = await OpenApiClient.createClient();
 37 | 
 38 |     // 请求 dataworks mcp tools json
 39 |     const dataWorksPopMcpTools: ActionTool[] = await getDataWorksPopMcpTools();
 40 | 
 41 |     // 请求 dataworks mcp resources
 42 |     const dwMcpRes = await getDataWorksMcp();
 43 | 
 44 |     const mcpActions = initDataWorksTools(dataWorksPopMcpTools, dwMcpRes);
 45 | 
 46 |     // 增加额外定义的 tools
 47 |     const extraTools = initExtraTools() || {};
 48 |     Object.keys(extraTools).forEach((k) => {
 49 |       if (!mcpActions[k] && extraTools[k]) mcpActions[k] = extraTools[k];
 50 |     });
 51 | 
 52 |     const serverOptions: ServerOptions = {
 53 |       capabilities: {
 54 |         resources: {
 55 |           subscribe: true,
 56 |           listChanged: true,
 57 |         },
 58 |         tools: {},
 59 |       },
 60 |       instructions: 'Operating with DataWorks Open APIs',
 61 |     };
 62 | 
 63 |     // https://spec.modelcontextprotocol.io/specification/2024-11-05/server/utilities/logging/
 64 |     if (serverOptions.capabilities && process.env.LOGGING_LEVEL) {
 65 |       serverOptions.capabilities.logging = {
 66 |         level: process.env.LOGGING_LEVEL,
 67 |         logFile: process.env.LOG_FILE,
 68 |       };
 69 |     }
 70 | 
 71 |     console.log('dataworks-mcp starting...');
 72 | 
 73 |     // Start the MCP server with error handling
 74 |     const serverWrapper = await startMcpServer(mcpActions, agent, {
 75 |       name: "dataworks-agent",
 76 |       version: mcpServerVersion,
 77 |       serverOptions,
 78 |     });
 79 | 
 80 |     const server = serverWrapper?.server;
 81 | 
 82 |     // List available resources
 83 |     await initResources(server, dataWorksPopMcpTools, dwMcpRes);
 84 | 
 85 |   } catch (error) {
 86 |     console.error('Failed to start MCP server:', error instanceof Error ? error.message : String(error));
 87 |     process.exit(1);
 88 |   }
 89 | }
 90 | 
 91 | // Handle uncaught exceptions and rejections
 92 | process.on('uncaughtException', (error) => {
 93 |   console.error('Uncaught Exception:', error);
 94 |   process.exit(1);
 95 | });
 96 | 
 97 | process.on('unhandledRejection', (reason, promise) => {
 98 |   console.error('Unhandled Rejection at:', promise, 'reason:', reason);
 99 |   process.exit(1);
100 | });
101 | 
102 | main();
```

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

```typescript
  1 | 
  2 | import convertTimestamps from '../tools/utils/convertTimestamps.js';
  3 | import toTimestamps from '../tools/utils/toTimestamps.js';
  4 | import { ActionTool } from '../types/action.js';
  5 | import { convertInputSchemaToSchema } from './initDataWorksTools.js';
  6 | 
  7 | /** 增加一些额外的帮助 Tools */
  8 | const initExtraTools = (options?: { categories?: string[]; names?: string[]; }) => {
  9 | 
 10 |   const actionMap: { [name: string]: ActionTool } = {};
 11 | 
 12 |   try {
 13 | 
 14 |     // 将 timestamp 转成 date */
 15 |     let actionKey = 'ConvertTimestamps';
 16 |     let action: ActionTool = {
 17 |       name: actionKey,
 18 |       description: '将时间戳转成日期或时间。返回内容如果有时间戳,透过此Tool显示成为日期或时间。',
 19 |       schema: convertInputSchemaToSchema({
 20 |         type: 'object',
 21 |         properties: {
 22 |           Timestamps: {
 23 |             type: 'array',
 24 |             description: '时间戳数组',
 25 |             items: {
 26 |               type: 'integer',
 27 |               description: '时间戳,如:1743422516765',
 28 |             }
 29 |           },
 30 |           Format: {
 31 |             type: 'string',
 32 |             description: '日期格式,如:YYYY-MM-DD HH:mm:ss',
 33 |           },
 34 |         },
 35 |         required: ['Timestamps'],
 36 |       }),
 37 |       handler: async (agent, input) => {
 38 |         try {
 39 |           const { Timestamps, Format } = input;
 40 |           const response = await convertTimestamps(agent, Timestamps, Format);
 41 |           return response as any;
 42 |         } catch (error: any) {
 43 |           // Handle specific Perplexity API error types
 44 |           if (error.response) {
 45 |             const { status, data } = error.response;
 46 |             if (status === 429) {
 47 |               return {
 48 |                 statusCode: status,
 49 |                 body: "Error: Rate limit exceeded. Please try again later.",
 50 |               };
 51 |             }
 52 |             return {
 53 |               statusCode: status,
 54 |               body: `Error: ${data.error?.message || error.message}`,
 55 |             };
 56 |           }
 57 |           return {
 58 |             body: `Failed to get information: ${error.message}`,
 59 |           };
 60 |         }
 61 |       }
 62 |     };
 63 |     actionMap[actionKey] = action;
 64 | 
 65 |     // 将 display 转成 timestamp */
 66 |     actionKey = 'ToTimestamps';
 67 |     action = {
 68 |       name: actionKey,
 69 |       description: '将日期或时间转成时间戳。',
 70 |       schema: convertInputSchemaToSchema({
 71 |         type: 'object',
 72 |         properties: {
 73 |           DateTimeDisplay: {
 74 |             type: 'array',
 75 |             description: '日期或时间数组',
 76 |             items: {
 77 |               type: 'string',
 78 |               description: '日期或时间,如:2025-01-02 或 2025-01-01 12:11:00',
 79 |             }
 80 |           },
 81 |         },
 82 |         required: ['DateTimeDisplay'],
 83 |       }),
 84 |       handler: async (agent, input) => {
 85 |         try {
 86 |           const { DateTimeDisplay } = input;
 87 |           const response = await toTimestamps(agent, DateTimeDisplay);
 88 |           return response as any;
 89 |         } catch (error: any) {
 90 |           // Handle specific Perplexity API error types
 91 |           if (error.response) {
 92 |             const { status, data } = error.response;
 93 |             if (status === 429) {
 94 |               return {
 95 |                 statusCode: status,
 96 |                 body: "Error: Rate limit exceeded. Please try again later.",
 97 |               };
 98 |             }
 99 |             return {
100 |               statusCode: status,
101 |               body: `Error: ${data.error?.message || error.message}`,
102 |             };
103 |           }
104 |           return {
105 |             body: `Failed to get information: ${error.message}`,
106 |           };
107 |         }
108 |       }
109 |     };
110 |     actionMap[actionKey] = action;
111 | 
112 |     return actionMap;
113 |   } catch (e) {
114 |     console.error(e);
115 |     return {};
116 |   }
117 | };
118 | 
119 | export default initExtraTools;
120 | 
121 | 
```

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

```typescript
  1 | import { getHandler } from '../actions/index.js';
  2 | import { ActionTool, DwInputSchema } from '../types/action.js';
  3 | import { z } from "zod";
  4 | import { ApiParameter, ApiParameterSchema } from '../types/alibabaCloudApi.js';
  5 | import { DataWorksMCPResponse } from '../types/common.js';
  6 | 
  7 | export const getZObjByType = (item?: ApiParameterSchema) => {
  8 | 
  9 |   let obj: any;
 10 | 
 11 |   const type = item?.type?.toLocaleLowerCase?.();
 12 | 
 13 |   if (type?.includes?.('int')) {
 14 |     // obj = z.bigint();
 15 |     obj = z.number();
 16 |   } if (type?.includes?.('double') || type?.includes?.('float')) {
 17 |     obj = z.number();
 18 |   } else if (type?.includes?.('string')) {
 19 |     obj = z.string();
 20 |   } else if (type?.includes?.('boolean')) {
 21 |     obj = z.boolean();
 22 |   } else if (type?.includes?.('date')) {
 23 |     obj = z.date();
 24 |   } else if (type?.includes?.('array')) {
 25 |     if (item?.items) {
 26 |       const childrenObjType = getZObjByType(item.items);
 27 |       obj = z.array(childrenObjType);
 28 |     } else obj = z.any();
 29 |   } else if (type?.includes?.('object')) {
 30 | 
 31 |     if (item?.properties) {
 32 |       const schema: { [name: string]: any } = {};
 33 |       Object.keys(item?.properties || {})?.forEach?.((paramName) => {
 34 |         const param = item?.properties?.[paramName];
 35 |         if (paramName) {
 36 |           let obj = getZObjByType(param);
 37 |           if (param?.description) obj = obj?.describe?.(param?.description);
 38 |           if (param?.required === false) {
 39 |             obj = obj?.optional?.();
 40 |             // 有 bug 需要执行两次
 41 |             if (obj?.optional) obj = obj?.optional?.();
 42 |           }
 43 |           schema[paramName] = obj;
 44 |         }
 45 |       });
 46 |       obj = z.object(schema);
 47 |     } else obj = z.any();
 48 | 
 49 |   } else {
 50 |     obj = z.any();
 51 |   }
 52 | 
 53 |   return obj;
 54 | }
 55 | 
 56 | export const convertInputSchemaToSchema = (inputSchema?: DwInputSchema, apiParameters?: ApiParameter[]) => {
 57 |   const schema: { [name: string]: any } = {};
 58 | 
 59 |   if (!inputSchema) return z.object(schema);
 60 | 
 61 |   const propertyKeys = Object.keys(inputSchema?.properties || {});
 62 |   const required = inputSchema?.required || [];
 63 | 
 64 |   // 先处理 required
 65 |   propertyKeys?.sort?.((a, b) => {
 66 |     if (required?.includes?.(a) && !required?.includes?.(b)) {
 67 |       return -1;
 68 |     } else if (!required?.includes?.(a) && required?.includes?.(b)) {
 69 |       return 1;
 70 |     } else {
 71 |       return 0;
 72 |     }
 73 |   });
 74 | 
 75 |   propertyKeys?.forEach?.((pKey) => {
 76 | 
 77 |     const info = inputSchema?.properties?.[pKey];
 78 |     const description = info?.description;
 79 | 
 80 |     let obj = getZObjByType(info as ApiParameterSchema);
 81 | 
 82 |     if (description) obj = obj?.describe?.(description);
 83 | 
 84 |     if (!required?.includes?.(pKey)) {
 85 |       obj = obj?.optional?.();
 86 |       // 有 bug 需要执行两次
 87 |       if (obj?.optional) obj = obj?.optional?.();
 88 |     }
 89 | 
 90 |     schema[pKey] = obj;
 91 | 
 92 |   });
 93 | 
 94 |   return z.object(schema);
 95 | 
 96 | };
 97 | 
 98 | /** 此方法是将 dw mcp 接口转成 action */
 99 | const initDataWorksTools = (dwTools: ActionTool[], dwMcpRes?: DataWorksMCPResponse) => {
100 | 
101 |   const actionMap: { [name: string]: ActionTool } = {};
102 | 
103 |   try {
104 | 
105 |     // DW 资源的白名单
106 |     const dwWhiteList: string[] = dwMcpRes?.result?.a2reslist || [];
107 | 
108 |     dwTools?.forEach?.((t) => {
109 |       const apiKey = t?.name;
110 | 
111 |       // 先过滤掉几个,方便调试
112 |       // if (!['CreateDataServiceApi'].includes(apiKey)) return;
113 | 
114 |       if (apiKey) {
115 | 
116 |         const map: ActionTool = { ...t };
117 | 
118 |         if (t?.inputSchema && !t?.schema) {
119 |           const apiMeta = (Object.keys(t?.annotations?.pmd || {})?.map?.((apiKey) => (t?.annotations?.pmd?.[apiKey])) || []) as ApiParameter[];
120 |           map.schema = convertInputSchemaToSchema(t?.inputSchema, apiMeta);
121 |         }
122 | 
123 |         if (!map?.handler) {
124 |           map.handler = getHandler(apiKey, t) as any;
125 |         }
126 | 
127 |         // 查看是否有对应的 MCP Resource
128 |         map.hasMcpResource = dwWhiteList?.includes?.(t?.name);
129 | 
130 |         actionMap[t?.name] = map;
131 | 
132 |       }
133 | 
134 |     });
135 | 
136 |     return actionMap;
137 |   } catch (e) {
138 |     console.error(e);
139 |     return {};
140 |   }
141 | };
142 | 
143 | export default initDataWorksTools;
144 | 
145 | 
```

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

```typescript
  1 | import isNumber from 'lodash/isNumber.js';
  2 | import isString from 'lodash/isString.js';
  3 | import { parse, stringify } from 'lossless-json';
  4 | import { BigNumber } from "bignumber.js";
  5 | 
  6 | /**
  7 |  * 是 undefined 或 null
  8 |  * @param value
  9 |  * @returns
 10 |  */
 11 | export function isEmpty(value: any) {
 12 |   return value === undefined || value === null;
 13 | }
 14 | 
 15 | /**
 16 |  * 是undefined null 空字串
 17 |  * @param input
 18 |  * @returns {boolean}
 19 |  */
 20 | export function isEmptyStr(input: any) {
 21 |   return (input === null || input === undefined || input === '');
 22 | }
 23 | 
 24 | export function isPreMode() {
 25 |   let pre: boolean = false;
 26 |   try {
 27 |     const env = process?.env || {};
 28 |     pre = env.NODE_ENV === 'development';
 29 |   } catch (e) {
 30 |     console.error(e);
 31 |   }
 32 |   return pre;
 33 | }
 34 | 
 35 | export function isVerboseMode() {
 36 |   let verbose: boolean = false;
 37 |   try {
 38 |     const env = process?.env || {};
 39 |     verbose = env.VERBOSE === 'true';
 40 |   } catch (e) {
 41 |     console.error(e);
 42 |   }
 43 |   return verbose;
 44 | }
 45 | 
 46 | export function getEnvRegion() {
 47 |   let regionId: string = '';
 48 |   try {
 49 |     regionId = process.env.DATAWORKS_REGION || process.env.REGION || '';
 50 |   } catch (e) {
 51 |     console.error(e);
 52 |   }
 53 |   return regionId;
 54 | }
 55 | 
 56 | export function getEnvInfo() {
 57 |   let envInfoStr: string = '';
 58 |   try {
 59 |     const env = process?.env || {};
 60 |     envInfoStr = toJSONString(env);
 61 |   } catch (e) {
 62 |     console.error(e);
 63 |   }
 64 |   return envInfoStr;
 65 | }
 66 | 
 67 | /** 检查 number 是否超过最大值,如果超过就用 BigInt */
 68 | export function getNumberString(v: number) {
 69 |   let result: number | string = v;
 70 |   try {
 71 |     if (!isNumber(v)) return result;
 72 |     if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {
 73 |       result = String(v);
 74 |     }
 75 |   } catch (e) {
 76 |     console.error(e);
 77 |   }
 78 |   return result;
 79 | }
 80 | 
 81 | /** 检查 number 是否超过最大值,如果超过就用 BigInt */
 82 | export function getNumber(v: number) {
 83 |   let result: number | BigInt = v;
 84 |   try {
 85 |     if (!isNumber(v)) return result;
 86 |     if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {
 87 |       result = BigInt(v);
 88 |     }
 89 |   } catch (e) {
 90 |     console.error(e);
 91 |   }
 92 |   return result;
 93 | }
 94 | 
 95 | /** 检查 number 是否超过最大值,如果超过就用 BigNumber (string) */
 96 | export function getBigNumber(v: number) {
 97 |   let result: number | BigNumber = v;
 98 |   try {
 99 |     if (!isNumber(v)) return result;
100 |     if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {
101 |       result = new BigNumber(v);
102 |     }
103 |   } catch (e) {
104 |     console.error(e);
105 |   }
106 |   return result;
107 | }
108 | 
109 | export function getMcpResourceName(params: { toolName?: string; }) {
110 |   return params?.toolName || '';
111 | }
112 | 
113 | export function isBigNumber(num: number) {
114 |   try {
115 |     return !Number.isSafeInteger(+num);
116 |   } catch (e) {
117 |     console.error(e);
118 |     return true;
119 |   }
120 | };
121 | 
122 | /** 将 string 里有大于 Number.MAX_SAFE_INTEGER 的数字转换为 特别的string */
123 | export function parseJSONForBigNumber(jsonString: string, prefix = '__big_number__') {
124 |   let result;
125 |   try {
126 |     // 自定义 reviver 函数
127 |     function bigIntReviver(key: string, value: any) {
128 |       if (isNumber(value) && isBigNumber(value)) {
129 |         return `${prefix}${value}`;
130 |       }
131 |       return value;
132 |     }
133 |     result = JSON.parse(jsonString, bigIntReviver);
134 |   } catch (e) {
135 |     console.error(e);
136 |   }
137 |   return result;
138 | }
139 | 
140 | /** 将值开头为 __big_number__ 转回正常的值 */
141 | export function stringifyJSONForBigNumber(json: any, prefix = '__big_number__') {
142 |   let result: string = '';
143 |   try {
144 |     function replacer(key: string, value: any) {
145 |       if (isString(value) && value.startsWith(prefix)) {
146 |         return value.slice(prefix.length);
147 |       }
148 |       return value;
149 |     }
150 |     result = JSON.stringify(json, replacer);
151 | 
152 |   } catch (e) {
153 |     console.error(e);
154 |   }
155 |   return result;
156 | }
157 | 
158 | /** 处理 big number 问题 */
159 | export function parseJSONString(jsonString: string) {
160 |   let result: any;
161 |   try {
162 |     result = parse(jsonString);
163 |   } catch (e) {
164 |     console.error(e);
165 |   }
166 |   return result;
167 | }
168 | 
169 | /** 处理 big number 问题 */
170 | export function toJSONString(json: any, replacer?: (number | string)[] | null, space?: string | number) {
171 |   let result: string = '';
172 |   try {
173 |     result = stringify(json, replacer, space) || '';
174 |   } catch (e) {
175 |     console.error(e);
176 |   }
177 |   return result;
178 | }
179 | 
```

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

```typescript
  1 | import isString from 'lodash/isString.js';
  2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  4 | import { z } from "zod";
  5 | import { OpenApiClientInstance } from "../openApiClient/index.js";
  6 | import { MCPSchemaShape, zodToMCPShape } from "../utils/zodToMCPSchema.js";
  7 | import type { ActionExample, ActionTool } from "../types/action.js";
  8 | import { convertInputSchemaToSchema } from "../utils/initDataWorksTools.js";
  9 | import { ServerOptions } from "@modelcontextprotocol/sdk/server/index.js";
 10 | import { getMcpResourceName, toJSONString } from "../utils/common.js";
 11 | 
 12 | /**
 13 |  * Creates an MCP server from a set of actions
 14 |  */
 15 | export function createMcpServer(
 16 |   actions: Record<string, ActionTool>,
 17 |   agent: OpenApiClientInstance,
 18 |   options: {
 19 |     name: string;
 20 |     version: string;
 21 |     serverOptions?: ServerOptions;
 22 |   }
 23 | ) {
 24 | 
 25 |   const serverOptions: ServerOptions = options?.serverOptions || {};
 26 | 
 27 |   // Create MCP server instance
 28 |   const serverWrapper = new McpServer({
 29 |     name: options.name,
 30 |     version: options.version,
 31 |   }, serverOptions);
 32 | 
 33 |   // Convert each action to an MCP tool
 34 |   for (const [key, action] of Object.entries(actions)) {
 35 | 
 36 |     let paramsSchema: MCPSchemaShape = {};
 37 |     if (action?.schema) {
 38 |       const { result = {} } = action?.schema ? zodToMCPShape(action.schema) : {};
 39 |       paramsSchema = result;
 40 |     } else {
 41 |       const { result = {} } = zodToMCPShape(convertInputSchemaToSchema(action?.inputSchema));
 42 |       paramsSchema = result;
 43 |     }
 44 | 
 45 |     console.log('Active tool', action.name);
 46 | 
 47 |     let actionDescription = action.description || '';
 48 | 
 49 |     // 如果有对应的 MCP Resource,需要放在 description 给模型提示
 50 |     if (action.hasMcpResource) {
 51 |       actionDescription += `\n*This Tool has a 'MCP Resource',please request ${getMcpResourceName({ toolName: action.name })}(MCP Resource) to get more examples for using this tool.`;
 52 |     }
 53 | 
 54 |     serverWrapper.tool(
 55 |       action.name,
 56 |       actionDescription,
 57 |       paramsSchema,
 58 |       async (params) => {
 59 |         try {
 60 |           // Execute the action handler with the params directly
 61 |           const result = await action?.handler?.(agent, params);
 62 | 
 63 |           // Format the result as MCP tool response
 64 |           return {
 65 |             content: [
 66 |               {
 67 |                 type: "text",
 68 |                 text: result ? (isString(result) ? result : toJSONString(result, null, 2)) : '',
 69 |               }
 70 |             ]
 71 |           };
 72 |         } catch (error) {
 73 |           console.error("error", error);
 74 |           // Handle errors in MCP format
 75 |           return {
 76 |             isError: true,
 77 |             content: [
 78 |               {
 79 |                 type: "text",
 80 |                 text: error instanceof Error ? error.message : "Unknown error occurred"
 81 |               }
 82 |             ]
 83 |           };
 84 |         }
 85 |       }
 86 |     );
 87 | 
 88 |     // Add examples as prompts if they exist
 89 |     if (action.examples && action.examples.length > 0) {
 90 |       serverWrapper.prompt(
 91 |         `${action.name}-examples`,
 92 |         {
 93 |           showIndex: z.string().optional().describe("Example index to show (number)")
 94 |         },
 95 |         (args) => {
 96 |           const showIndex = args.showIndex ? parseInt(args.showIndex) : undefined;
 97 |           const examples = action?.examples?.flat?.();
 98 |           const selectedExamples = (typeof showIndex === 'number'
 99 |             ? [examples?.[showIndex]]
100 |             : examples) as ActionExample[];
101 | 
102 |           const exampleText = selectedExamples?.map((ex, idx) => `
103 | Example ${idx + 1}:
104 | Input: ${toJSONString(ex?.input, null, 2)}
105 | Output: ${toJSONString(ex?.output, null, 2)}
106 | Explanation: ${ex?.explanation}
107 |             `)
108 |             .join('\n');
109 | 
110 |           return {
111 |             messages: [
112 |               {
113 |                 role: "user",
114 |                 content: {
115 |                   type: "text",
116 |                   text: `Examples for ${action.name}:\n${exampleText}`
117 |                 }
118 |               }
119 |             ]
120 |           };
121 |         }
122 |       );
123 |     }
124 |   }
125 | 
126 |   return serverWrapper;
127 | }
128 | /**
129 |  * Helper to start the MCP server with stdio transport
130 |  * 
131 |  * @param actions - The actions to expose to the MCP server
132 |  * @param agent - Aliyun Open API client instance
133 |  * @param options - The options for the MCP server
134 |  * @returns The MCP server
135 |  * @throws Error if the MCP server fails to start
136 |  * @example 
137 |  * import { ACTIONS } from "./actions";
138 |  * import { startMcpServer } from "./mcpWrapper";
139 |  *
140 |  * const agent =  OpenApiClient.createClient({
141 |         REGION: process.env.REGION || "",
142 |         ALIBABA_CLOUD_ACCESS_KEY_ID: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID || "",
143 |         ALIBABA_CLOUD_ACCESS_KEY_SECRET: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET || "",
144 |       });
145 |  * 
146 |  * startMcpServer(ACTIONS, agent, {
147 |  *   name: "dataworks-actions",
148 |  *   version: "1.0.0"
149 |  * });
150 |  */
151 | export async function startMcpServer(
152 |   actions: Record<string, ActionTool>,
153 |   agent: OpenApiClientInstance,
154 |   options: {
155 |     name: string;
156 |     version: string;
157 |     serverOptions?: ServerOptions;
158 |   }
159 | ) {
160 |   try {
161 |     const serverWrapper = createMcpServer(actions, agent, options);
162 |     const transport = new StdioServerTransport();
163 |     await serverWrapper.connect(transport);
164 | 
165 |     if (process.env.LOGGING_LEVEL) {
166 |       serverWrapper?.server?.sendLoggingMessage?.({
167 |         level: process.env.LOGGING_LEVEL as any,
168 |         data: "Server started successfully",
169 |       });
170 |     }
171 | 
172 |     return serverWrapper;
173 |   } catch (error) {
174 |     console.error("Error starting MCP server", error);
175 |     throw error;
176 |   }
177 | }
178 | 
```

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

```typescript
  1 | // import DataWorksPublic20240518 from '@alicloud/dataworks-public20240518';
  2 | // import * as DataWorksPublic20240518Classes from '@alicloud/dataworks-public20240518';
  3 | import fetch from 'node-fetch';
  4 | import OpenApi from '@alicloud/openapi-client';
  5 | import OpenApiUtil from '@alicloud/openapi-util';
  6 | import Util from '@alicloud/tea-util';
  7 | import tea from '@alicloud/tea-typescript';
  8 | import record from '../utils/record.js';
  9 | import isNumber from 'lodash/isNumber.js';
 10 | import isString from 'lodash/isString.js';
 11 | import isObject from 'lodash/isObject.js';
 12 | import { OpenApiClientInstance } from "../openApiClient/index.js";
 13 | import { IAlibabaCloudOpenApiJsonResponse, OpenApiConfigs } from '../types/alibabaCloudApi.js';
 14 | import { ActionTool } from '../types/action.js';
 15 | import { isEmptyStr, isVerboseMode, getEnvInfo, toJSONString, parseJSONString, isBigNumber } from '../utils/common.js';
 16 | 
 17 | /**
 18 |  * Get detailed and latest information about any topic using Perplexity AI.
 19 |  * @param agent Aliyun Open API instance
 20 |  * @param prompt Text description of the topic to get information about
 21 |  * @returns Object containing the generated information
 22 |  */
 23 | async function callTool(
 24 |   agent: OpenApiClientInstance,
 25 |   apiKey: string,
 26 |   actionTool: ActionTool,
 27 |   input?: Record<string, any>,
 28 | ) {
 29 | 
 30 |   let apiRequestConfigs: OpenApi.Params = {} as any;
 31 |   let query: any = {};
 32 |   let body: any = {};
 33 | 
 34 |   // API版本号
 35 |   const version = actionTool?.annotations?.version;
 36 | 
 37 |   try {
 38 | 
 39 |     // 原来使用特定调用的方式
 40 |     // import * as DataWorksPublic20240518Classes from '@alicloud/dataworks-public20240518';
 41 |     // const FunctionClass = (DataWorksPublic20240518Classes as any)[`${apiKey}Request`];
 42 |     // const request = Reflect.construct(FunctionClass, input as any);
 43 |     // const runtime = new Util.RuntimeOptions({});
 44 |     // // 把apiKey第一个大小改小写
 45 |     // const funcName = `${apiKey.charAt(0).toLowerCase()}${apiKey.slice(1) || ''}WithOptions`;
 46 |     // return await (agent as any)[funcName](request, runtime);
 47 | 
 48 |     // 使用泛化方式调用
 49 |     // https://help.aliyun.com/zh/sdk/developer-reference/generalized-call-node-js
 50 | 
 51 |     // path 为空就是 RPC
 52 |     const style = isEmptyStr(actionTool?.annotations?.path) ? 'RPC' : 'ROA';
 53 | 
 54 |     const method = actionTool?.annotations?.method;
 55 | 
 56 |     let hasInQueryParams = false;
 57 |     let hasInBodyParams = false;
 58 |     let hasInByteParams = false;
 59 |     let hasInFormDataParams = false;
 60 | 
 61 |     // 需要重新 assign 下
 62 |     const _input: any = { ...input };
 63 | 
 64 |     Object.keys(_input)?.forEach((key) => {
 65 |       let value = _input[key];
 66 | 
 67 |       // if (isNumber(value)) {
 68 |       //   // 查看值有没有溢出,如果溢出了就用string
 69 |       //   if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) {
 70 |       //     value = String(value);
 71 |       //   }
 72 |       // }
 73 | 
 74 |       const paramMeta = actionTool?.annotations?.pmd?.[key];
 75 |       if (paramMeta?.in === 'body') {
 76 |         hasInBodyParams = true;
 77 |         body[key] = value;
 78 |       } else if (paramMeta?.in === 'formData') {
 79 |         hasInFormDataParams = true;
 80 |         if (paramMeta?.style === 'json') {
 81 |           body[key] = toJSONString(value);
 82 |         } else {
 83 |           body[key] = value;
 84 |         }
 85 |       } else if (paramMeta?.in === 'byte') {
 86 |         hasInByteParams = true;
 87 |         body[key] = value;
 88 |       } else if (paramMeta?.in === 'query') {
 89 |         hasInQueryParams = true;
 90 |         if (paramMeta?.style === 'json') {
 91 |           query[key] = toJSONString(value);
 92 |         } else {
 93 |           query[key] = value;
 94 |         }
 95 |       } else {
 96 |         query[key] = value;
 97 |       }
 98 |     });
 99 | 
100 |     const reqBodyType = hasInByteParams ? 'byte' : hasInFormDataParams ? 'formData' : 'json';
101 | 
102 |     const bodyJson = toJSONString(body);
103 |     if (reqBodyType === 'byte') {
104 |       body = Buffer.from(bodyJson);
105 |     } else if (reqBodyType === 'json') {
106 |       body = bodyJson;
107 |     }
108 | 
109 |     const configs: OpenApiConfigs = {
110 |       style, // API风格
111 |       action: apiKey, // API 名称
112 |       version,
113 |       protocol: 'HTTPS', // API协议
114 |       method, // 请求方法
115 |       authType: 'AK',
116 |       pathname: `/`, // 接口 PATH
117 |       reqBodyType, // 接口请求体内容格式
118 |       bodyType: 'string', // 使用 string,如果使用 json 在 sdk 里会有精度丢失的问题
119 |     };
120 | 
121 |     if (['GET', 'DELETE'].includes(method || '')) delete configs.reqBodyType;
122 | 
123 |     apiRequestConfigs = new OpenApi.Params(configs);
124 | 
125 |     // GET DELETE 这边需要把 body 设定为空,不然签名不会过
126 |     if (['GET', 'DELETE'].includes(method || '')) body = null;
127 | 
128 |     const request = new OpenApi.OpenApiRequest({ query, body });
129 |     // runtime default settings https://github.com/aliyun/tea-util/blob/5f4bdebef3b57d33207b6bc44af6ed5e1a009959/ts/test/client.spec.ts#L133
130 |     const runtime = new Util.RuntimeOptions({
131 |       readTimeout: process.env.OPEN_API_READ_TIMEOUT ? Number(process.env.OPEN_API_READ_TIMEOUT) : 10000,
132 |       connectTimeout: process.env.OPEN_API_CONNECT_TIMEOUT ? Number(process.env.OPEN_API_CONNECT_TIMEOUT) : 10000,
133 |     });
134 |     // 查看 https://github.com/aliyun/darabonba-openapi/blob/master/ts/src/client.ts
135 |     const res = await (agent as any)?.callApi?.(apiRequestConfigs, request, runtime);
136 |     let result: IAlibabaCloudOpenApiJsonResponse['body'] | null = null;
137 |     try {
138 |       // 当执行成功,只取 message.body 的信息
139 |       const obj = res?.statusCode === 200 && res?.body ? res?.body : res;
140 |       result = parseJSONString(obj);
141 |       await record({ success: true, toolName: apiKey, requestId: result?.RequestId, version });
142 |     } catch (e) {
143 |       console.error(e);
144 |     }
145 | 
146 |     return result;
147 | 
148 |   } catch (error: any) {
149 |     const verbose = isVerboseMode();
150 |     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()}` : ''}`;
151 |     const recordErr = `Call tool failed: ${error.message}, api key: ${apiKey}, api request configs: ${toJSONString(apiRequestConfigs)}, query: ${toJSONString(query)}, body: ${toJSONString(body)}`;
152 |     await record({ success: false, toolName: apiKey, version, error: recordErr });
153 |     throw new Error(errorMsg);
154 |   }
155 | };
156 | 
157 | export default callTool;
158 | 
```

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

```
  1 |                                  Apache License
  2 |                            Version 2.0, January 2004
  3 |                         http://www.apache.org/licenses/
  4 | 
  5 |    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
  6 | 
  7 |    1. Definitions.
  8 | 
  9 |       "License" shall mean the terms and conditions for use, reproduction,
 10 |       and distribution as defined by Sections 1 through 9 of this document.
 11 | 
 12 |       "Licensor" shall mean the copyright owner or entity authorized by
 13 |       the copyright owner that is granting the License.
 14 | 
 15 |       "Legal Entity" shall mean the union of the acting entity and all
 16 |       other entities that control, are controlled by, or are under common
 17 |       control with that entity. For the purposes of this definition,
 18 |       "control" means (i) the power, direct or indirect, to cause the
 19 |       direction or management of such entity, whether by contract or
 20 |       otherwise, or (ii) ownership of fifty percent (50%) or more of the
 21 |       outstanding shares, or (iii) beneficial ownership of such entity.
 22 | 
 23 |       "You" (or "Your") shall mean an individual or Legal Entity
 24 |       exercising permissions granted by this License.
 25 | 
 26 |       "Source" form shall mean the preferred form for making modifications,
 27 |       including but not limited to software source code, documentation
 28 |       source, and configuration files.
 29 | 
 30 |       "Object" form shall mean any form resulting from mechanical
 31 |       transformation or translation of a Source form, including but
 32 |       not limited to compiled object code, generated documentation,
 33 |       and conversions to other media types.
 34 | 
 35 |       "Work" shall mean the work of authorship, whether in Source or
 36 |       Object form, made available under the License, as indicated by a
 37 |       copyright notice that is included in or attached to the work
 38 |       (an example is provided in the Appendix below).
 39 | 
 40 |       "Derivative Works" shall mean any work, whether in Source or Object
 41 |       form, that is based on (or derived from) the Work and for which the
 42 |       editorial revisions, annotations, elaborations, or other modifications
 43 |       represent, as a whole, an original work of authorship. For the purposes
 44 |       of this License, Derivative Works shall not include works that remain
 45 |       separable from, or merely link (or bind by name) to the interfaces of,
 46 |       the Work and Derivative Works thereof.
 47 | 
 48 |       "Contribution" shall mean any work of authorship, including
 49 |       the original version of the Work and any modifications or additions
 50 |       to that Work or Derivative Works thereof, that is intentionally
 51 |       submitted to Licensor for inclusion in the Work by the copyright owner
 52 |       or by an individual or Legal Entity authorized to submit on behalf of
 53 |       the copyright owner. For the purposes of this definition, "submitted"
 54 |       means any form of electronic, verbal, or written communication sent
 55 |       to the Licensor or its representatives, including but not limited to
 56 |       communication on electronic mailing lists, source code control systems,
 57 |       and issue tracking systems that are managed by, or on behalf of, the
 58 |       Licensor for the purpose of discussing and improving the Work, but
 59 |       excluding communication that is conspicuously marked or otherwise
 60 |       designated in writing by the copyright owner as "Not a Contribution."
 61 | 
 62 |       "Contributor" shall mean Licensor and any individual or Legal Entity
 63 |       on behalf of whom a Contribution has been received by Licensor and
 64 |       subsequently incorporated within the Work.
 65 | 
 66 |    2. Grant of Copyright License. Subject to the terms and conditions of
 67 |       this License, each Contributor hereby grants to You a perpetual,
 68 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 69 |       copyright license to reproduce, prepare Derivative Works of,
 70 |       publicly display, publicly perform, sublicense, and distribute the
 71 |       Work and such Derivative Works in Source or Object form.
 72 | 
 73 |    3. Grant of Patent License. Subject to the terms and conditions of
 74 |       this License, each Contributor hereby grants to You a perpetual,
 75 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 76 |       (except as stated in this section) patent license to make, have made,
 77 |       use, offer to sell, sell, import, and otherwise transfer the Work,
 78 |       where such license applies only to those patent claims licensable
 79 |       by such Contributor that are necessarily infringed by their
 80 |       Contribution(s) alone or by combination of their Contribution(s)
 81 |       with the Work to which such Contribution(s) was submitted. If You
 82 |       institute patent litigation against any entity (including a
 83 |       cross-claim or counterclaim in a lawsuit) alleging that the Work
 84 |       or a Contribution incorporated within the Work constitutes direct
 85 |       or contributory patent infringement, then any patent licenses
 86 |       granted to You under this License for that Work shall terminate
 87 |       as of the date such litigation is filed.
 88 | 
 89 |    4. Redistribution. You may reproduce and distribute copies of the
 90 |       Work or Derivative Works thereof in any medium, with or without
 91 |       modifications, and in Source or Object form, provided that You
 92 |       meet the following conditions:
 93 | 
 94 |       (a) You must give any other recipients of the Work or
 95 |           Derivative Works a copy of this License; and
 96 | 
 97 |       (b) You must cause any modified files to carry prominent notices
 98 |           stating that You changed the files; and
 99 | 
100 |       (c) You must retain, in the Source form of any Derivative Works
101 |           that You distribute, all copyright, patent, trademark, and
102 |           attribution notices from the Source form of the Work,
103 |           excluding those notices that do not pertain to any part of
104 |           the Derivative Works; and
105 | 
106 |       (d) If the Work includes a "NOTICE" text file as part of its
107 |           distribution, then any Derivative Works that You distribute must
108 |           include a readable copy of the attribution notices contained
109 |           within such NOTICE file, excluding those notices that do not
110 |           pertain to any part of the Derivative Works, in at least one
111 |           of the following places: within a NOTICE text file distributed
112 |           as part of the Derivative Works; within the Source form or
113 |           documentation, if provided along with the Derivative Works; or,
114 |           within a display generated by the Derivative Works, if and
115 |           wherever such third-party notices normally appear. The contents
116 |           of the NOTICE file are for informational purposes only and
117 |           do not modify the License. You may add Your own attribution
118 |           notices within Derivative Works that You distribute, alongside
119 |           or as an addendum to the NOTICE text from the Work, provided
120 |           that such additional attribution notices cannot be construed
121 |           as modifying the License.
122 | 
123 |       You may add Your own copyright statement to Your modifications and
124 |       may provide additional or different license terms and conditions
125 |       for use, reproduction, or distribution of Your modifications, or
126 |       for any such Derivative Works as a whole, provided Your use,
127 |       reproduction, and distribution of the Work otherwise complies with
128 |       the conditions stated in this License.
129 | 
130 |    5. Submission of Contributions. Unless You explicitly state otherwise,
131 |       any Contribution intentionally submitted for inclusion in the Work
132 |       by You to the Licensor shall be under the terms and conditions of
133 |       this License, without any additional terms or conditions.
134 |       Notwithstanding the above, nothing herein shall supersede or modify
135 |       the terms of any separate license agreement you may have executed
136 |       with Licensor regarding such Contributions.
137 | 
138 |    6. Trademarks. This License does not grant permission to use the trade
139 |       names, trademarks, service marks, or product names of the Licensor,
140 |       except as required for reasonable and customary use in describing the
141 |       origin of the Work and reproducing the content of the NOTICE file.
142 | 
143 |    7. Disclaimer of Warranty. Unless required by applicable law or
144 |       agreed to in writing, Licensor provides the Work (and each
145 |       Contributor provides its Contributions) on an "AS IS" BASIS,
146 |       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 |       implied, including, without limitation, any warranties or conditions
148 |       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 |       PARTICULAR PURPOSE. You are solely responsible for determining the
150 |       appropriateness of using or redistributing the Work and assume any
151 |       risks associated with Your exercise of permissions under this License.
152 | 
153 |    8. Limitation of Liability. In no event and under no legal theory,
154 |       whether in tort (including negligence), contract, or otherwise,
155 |       unless required by applicable law (such as deliberate and grossly
156 |       negligent acts) or agreed to in writing, shall any Contributor be
157 |       liable to You for damages, including any direct, indirect, special,
158 |       incidental, or consequential damages of any character arising as a
159 |       result of this License or out of the use or inability to use the
160 |       Work (including but not limited to damages for loss of goodwill,
161 |       work stoppage, computer failure or malfunction, or any and all
162 |       other commercial damages or losses), even if such Contributor
163 |       has been advised of the possibility of such damages.
164 | 
165 |    9. Accepting Warranty or Additional Liability. While redistributing
166 |       the Work or Derivative Works thereof, You may choose to offer,
167 |       and charge a fee for, acceptance of support, warranty, indemnity,
168 |       or other liability obligations and/or rights consistent with this
169 |       License. However, in accepting such obligations, You may act only
170 |       on Your own behalf and on Your sole responsibility, not on behalf
171 |       of any other Contributor, and only if You agree to indemnify,
172 |       defend, and hold each Contributor harmless for any liability
173 |       incurred by, or claims asserted against, such Contributor by reason
174 |       of your accepting any such warranty or additional liability.
175 | 
176 |    END OF TERMS AND CONDITIONS
177 | 
178 |    APPENDIX: How to apply the Apache License to your work.
179 | 
180 |       To apply the Apache License to your work, attach the following
181 |       boilerplate notice, with the fields enclosed by brackets "[]"
182 |       replaced with your own identifying information. (Don't include
183 |       the brackets!)  The text should be enclosed in the appropriate
184 |       comment syntax for the file format. We also recommend that a
185 |       file or class name and description of purpose be included on the
186 |       same "printed page" as the copyright notice for easier
187 |       identification within third-party archives.
188 | 
189 |    Copyright [yyyy] [name of copyright owner]
190 | 
191 |    Licensed under the Apache License, Version 2.0 (the "License");
192 |    you may not use this file except in compliance with the License.
193 |    You may obtain a copy of the License at
194 | 
195 |        http://www.apache.org/licenses/LICENSE-2.0
196 | 
197 |    Unless required by applicable law or agreed to in writing, software
198 |    distributed under the License is distributed on an "AS IS" BASIS,
199 |    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 |    See the License for the specific language governing permissions and
201 |    limitations under the License.
202 | 
```