Skip to content

Commit ad028a7

Browse files
authored
update src
1 parent 73fd91d commit ad028a7

19 files changed

+1537
-0
lines changed

src/actions/index.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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";

src/constants/index.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { getEnvRegion } from "../utils/common.js";
2+
3+
export const defaultDataWorksAliyunOpenApiVersion = '2024-05-18';
4+
/** dataworks 全部线上 open api 元数据 */
5+
export const dataWorksAliyunAllOpenApiUrl = `https://next.api.aliyun.com/meta/v1/products/dataworks-public/versions/${defaultDataWorksAliyunOpenApiVersion}/api-docs.json?file=api-docs.json`;
6+
/** dataworks mcp tools 线上元数据接口 */
7+
export const dataWorksPopMcpToolsUrl = 'https://dataworks.data.aliyun.com/pop-mcp-tools';
8+
/** dataworks mcp tools 预发元数据接口 */
9+
export const dataWorksPrePopMcpToolsUrl = 'https://pre-dataworks.data.aliyun.com/pop-mcp-tools';
10+
/** dataworks mcp 线上元数据接口 */
11+
export const dataWorksMcpUrl = 'https://dataworks.data.aliyun.com/mcp';
12+
/** dataworks mcp 预发元数据接口 */
13+
export const dataWorksPreMcpUrl = 'https://pre-dataworks.data.aliyun.com/mcp';
14+
15+
16+
/** dataworks mcp record 接口 */
17+
export const dataWorksRecordUrl = 'https://dataworks.data.aliyun.com/mcp';
18+
/** dataworks mcp record 预发接口 */
19+
export const dataWorksPreRecordUrl = 'https://pre-dataworks.data.aliyun.com/mcp';
20+
21+
/** 参数为 url */
22+
export const convertOAS2ToOAS3Url = 'https://converter.swagger.io/api/convert';
23+
24+
/** mcp server version */
25+
export const mcpServerVersion = '0.0.2';

src/index.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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/mcp/index.ts

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
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*此Tool有MCP Resource,请查看${getMcpResourceName({ toolName: action.name })}(MCP Resource)获取更多使用此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+
}

0 commit comments

Comments
 (0)