# 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
[](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.
```