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