@newfold/wp-module-ai-chat 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -0
- package/package.json +51 -0
- package/src/components/chat/ChatHeader.jsx +63 -0
- package/src/components/chat/ChatHistoryDropdown.jsx +182 -0
- package/src/components/chat/ChatHistoryList.jsx +257 -0
- package/src/components/chat/ChatInput.jsx +157 -0
- package/src/components/chat/ChatMessage.jsx +157 -0
- package/src/components/chat/ChatMessages.jsx +137 -0
- package/src/components/chat/WelcomeScreen.jsx +115 -0
- package/src/components/icons/CloseIcon.jsx +27 -0
- package/src/components/icons/SparklesOutlineIcon.jsx +30 -0
- package/src/components/icons/index.js +5 -0
- package/src/components/ui/AILogo.jsx +47 -0
- package/src/components/ui/BluBetaHeading.jsx +18 -0
- package/src/components/ui/ErrorAlert.jsx +30 -0
- package/src/components/ui/HeaderBar.jsx +34 -0
- package/src/components/ui/SuggestionButton.jsx +28 -0
- package/src/components/ui/ToolExecutionList.jsx +264 -0
- package/src/components/ui/TypingIndicator.jsx +268 -0
- package/src/constants/nfdAgents/input.js +13 -0
- package/src/constants/nfdAgents/storageKeys.js +102 -0
- package/src/constants/nfdAgents/typingStatus.js +40 -0
- package/src/constants/nfdAgents/websocket.js +44 -0
- package/src/hooks/useAIChat.js +432 -0
- package/src/hooks/useNfdAgentsWebSocket.js +964 -0
- package/src/index.js +66 -0
- package/src/services/mcpClient.js +433 -0
- package/src/services/openaiClient.js +416 -0
- package/src/styles/_branding.scss +151 -0
- package/src/styles/_history.scss +180 -0
- package/src/styles/_input.scss +170 -0
- package/src/styles/_messages.scss +272 -0
- package/src/styles/_mixins.scss +21 -0
- package/src/styles/_typing-indicator.scss +162 -0
- package/src/styles/_ui.scss +173 -0
- package/src/styles/_vars.scss +103 -0
- package/src/styles/_welcome.scss +81 -0
- package/src/styles/app.scss +10 -0
- package/src/utils/helpers.js +75 -0
- package/src/utils/markdownParser.js +319 -0
- package/src/utils/nfdAgents/archiveConversation.js +82 -0
- package/src/utils/nfdAgents/chatHistoryList.js +130 -0
- package/src/utils/nfdAgents/configFetcher.js +137 -0
- package/src/utils/nfdAgents/greeting.js +55 -0
- package/src/utils/nfdAgents/jwtUtils.js +59 -0
- package/src/utils/nfdAgents/messageHandler.js +328 -0
- package/src/utils/nfdAgents/storage.js +112 -0
- package/src/utils/nfdAgents/typingIndicatorToolDisplay.js +180 -0
- package/src/utils/nfdAgents/url.js +101 -0
- package/src/utils/restApi.js +87 -0
- package/src/utils/sanitizeHtml.js +94 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Chat Module - Main Entry Point
|
|
3
|
+
*
|
|
4
|
+
* This module provides reusable AI chat functionality for WordPress.
|
|
5
|
+
* Use this as a foundation for editor chat, help center chat, and other AI interfaces.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import "./styles/app.scss";
|
|
9
|
+
|
|
10
|
+
// Services
|
|
11
|
+
export { WordPressMCPClient, createMCPClient, mcpClient, MCPError } from "./services/mcpClient";
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
CloudflareOpenAIClient,
|
|
15
|
+
createOpenAIClient,
|
|
16
|
+
openaiClient,
|
|
17
|
+
OpenAIError,
|
|
18
|
+
} from "./services/openaiClient";
|
|
19
|
+
|
|
20
|
+
// Hooks
|
|
21
|
+
export { useAIChat, CHAT_STATUS } from "./hooks/useAIChat";
|
|
22
|
+
export { default as useNfdAgentsWebSocket } from "./hooks/useNfdAgentsWebSocket";
|
|
23
|
+
|
|
24
|
+
// Utils
|
|
25
|
+
export { simpleHash, generateSessionId, debounce } from "./utils/helpers";
|
|
26
|
+
export { containsMarkdown, parseMarkdown } from "./utils/markdownParser";
|
|
27
|
+
export { sanitizeHtml, containsHtml } from "./utils/sanitizeHtml";
|
|
28
|
+
|
|
29
|
+
// NFD Agents Utilities
|
|
30
|
+
export {
|
|
31
|
+
convertToWebSocketUrl,
|
|
32
|
+
normalizeUrl,
|
|
33
|
+
isLocalhost,
|
|
34
|
+
buildWebSocketUrl,
|
|
35
|
+
} from "./utils/nfdAgents/url";
|
|
36
|
+
export { isInitialGreeting } from "./utils/nfdAgents/greeting";
|
|
37
|
+
|
|
38
|
+
// Constants
|
|
39
|
+
export { NFD_AGENTS_WEBSOCKET } from "./constants/nfdAgents/websocket";
|
|
40
|
+
export { getChatHistoryStorageKeys } from "./constants/nfdAgents/storageKeys";
|
|
41
|
+
export { TYPING_STATUS } from "./constants/nfdAgents/typingStatus";
|
|
42
|
+
export { INPUT } from "./constants/nfdAgents/input";
|
|
43
|
+
|
|
44
|
+
// Chat Components
|
|
45
|
+
export { default as ChatMessage } from "./components/chat/ChatMessage";
|
|
46
|
+
export { default as ChatMessages } from "./components/chat/ChatMessages";
|
|
47
|
+
export { default as ChatInput } from "./components/chat/ChatInput";
|
|
48
|
+
export { default as ChatHeader } from "./components/chat/ChatHeader";
|
|
49
|
+
export { default as WelcomeScreen } from "./components/chat/WelcomeScreen";
|
|
50
|
+
|
|
51
|
+
// Chat history (consumer must match useNfdAgentsWebSocket for same consumer)
|
|
52
|
+
export {
|
|
53
|
+
archiveConversation,
|
|
54
|
+
removeConversationFromArchive,
|
|
55
|
+
} from "./utils/nfdAgents/archiveConversation";
|
|
56
|
+
export { default as ChatHistoryList } from "./components/chat/ChatHistoryList";
|
|
57
|
+
export { default as ChatHistoryDropdown } from "./components/chat/ChatHistoryDropdown";
|
|
58
|
+
|
|
59
|
+
// UI Components
|
|
60
|
+
export { default as AILogo } from "./components/ui/AILogo";
|
|
61
|
+
export { default as BluBetaHeading } from "./components/ui/BluBetaHeading";
|
|
62
|
+
export { default as HeaderBar } from "./components/ui/HeaderBar";
|
|
63
|
+
export { default as ErrorAlert } from "./components/ui/ErrorAlert";
|
|
64
|
+
export { default as SuggestionButton } from "./components/ui/SuggestionButton";
|
|
65
|
+
export { default as ToolExecutionList } from "./components/ui/ToolExecutionList";
|
|
66
|
+
export { default as TypingIndicator } from "./components/ui/TypingIndicator";
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress MCP Client
|
|
3
|
+
*
|
|
4
|
+
* MCP client implementation using the official TypeScript SDK
|
|
5
|
+
* for WordPress integration with nonce-based authentication.
|
|
6
|
+
* Configurable for use across different modules.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/* eslint-disable no-undef, no-console */
|
|
10
|
+
|
|
11
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
12
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Custom error class for MCP operations
|
|
16
|
+
*/
|
|
17
|
+
export class MCPError extends Error {
|
|
18
|
+
constructor(message, code = null, details = null) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "MCPError";
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.details = details;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* WordPress MCP Client class
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} options Configuration options
|
|
30
|
+
* @param {string} options.configKey - Window config object name (default: 'nfdAIChat')
|
|
31
|
+
*/
|
|
32
|
+
export class WordPressMCPClient {
|
|
33
|
+
constructor(options = {}) {
|
|
34
|
+
this.configKey = options.configKey || "nfdAIChat";
|
|
35
|
+
this.client = null;
|
|
36
|
+
this.transport = null;
|
|
37
|
+
this.connected = false;
|
|
38
|
+
this.tools = [];
|
|
39
|
+
this.resources = [];
|
|
40
|
+
this.eventListeners = new Map();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get WordPress configuration from global variable
|
|
45
|
+
*
|
|
46
|
+
* @return {Object} WordPress config
|
|
47
|
+
*/
|
|
48
|
+
getConfig() {
|
|
49
|
+
const config = (typeof window !== "undefined" && window[this.configKey]) || {};
|
|
50
|
+
return {
|
|
51
|
+
nonce: config.nonce || "",
|
|
52
|
+
restUrl: config.restUrl || "/wp-json/",
|
|
53
|
+
mcpUrl: config.mcpUrl || `${config.restUrl || "/wp-json/"}mcp/mcp-adapter-default-server`,
|
|
54
|
+
homeUrl: config.homeUrl || "",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Add event listener
|
|
60
|
+
*
|
|
61
|
+
* @param {string} event Event name
|
|
62
|
+
* @param {Function} listener Callback function
|
|
63
|
+
*/
|
|
64
|
+
on(event, listener) {
|
|
65
|
+
if (!this.eventListeners.has(event)) {
|
|
66
|
+
this.eventListeners.set(event, new Set());
|
|
67
|
+
}
|
|
68
|
+
this.eventListeners.get(event).add(listener);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Remove event listener
|
|
73
|
+
*
|
|
74
|
+
* @param {string} event Event name
|
|
75
|
+
* @param {Function} listener Callback function
|
|
76
|
+
*/
|
|
77
|
+
off(event, listener) {
|
|
78
|
+
const listeners = this.eventListeners.get(event);
|
|
79
|
+
if (listeners) {
|
|
80
|
+
listeners.delete(listener);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Emit event to listeners
|
|
86
|
+
*
|
|
87
|
+
* @param {Object} event Event object with type and optional data
|
|
88
|
+
*/
|
|
89
|
+
emit(event) {
|
|
90
|
+
const listeners = this.eventListeners.get(event.type);
|
|
91
|
+
if (listeners) {
|
|
92
|
+
listeners.forEach((listener) => {
|
|
93
|
+
try {
|
|
94
|
+
listener(event);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error("Error in MCP event listener:", error);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Connect to the MCP server
|
|
104
|
+
*
|
|
105
|
+
* @param {string} serverUrl Optional server URL override
|
|
106
|
+
* @return {Promise<void>}
|
|
107
|
+
*/
|
|
108
|
+
async connect(serverUrl = null) {
|
|
109
|
+
try {
|
|
110
|
+
const config = this.getConfig();
|
|
111
|
+
const mcpEndpoint = serverUrl || config.mcpUrl;
|
|
112
|
+
|
|
113
|
+
if (!mcpEndpoint) {
|
|
114
|
+
throw new MCPError("MCP endpoint URL not configured");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Initialize the MCP Client
|
|
118
|
+
this.client = new Client(
|
|
119
|
+
{
|
|
120
|
+
name: "nfd-ai-chat-client",
|
|
121
|
+
version: "1.0.0",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
capabilities: {},
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Create HTTP transport with WordPress authentication headers
|
|
129
|
+
this.transport = new StreamableHTTPClientTransport(new URL(mcpEndpoint), {
|
|
130
|
+
requestInit: {
|
|
131
|
+
headers: {
|
|
132
|
+
"X-WP-Nonce": config.nonce,
|
|
133
|
+
"Content-Type": "application/json",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Connect using the SDK
|
|
139
|
+
await this.client.connect(this.transport);
|
|
140
|
+
|
|
141
|
+
this.connected = true;
|
|
142
|
+
this.emit({ type: "connected" });
|
|
143
|
+
} catch (error) {
|
|
144
|
+
const mcpError =
|
|
145
|
+
error instanceof MCPError ? error : new MCPError(`Connection failed: ${error.message}`);
|
|
146
|
+
this.emit({ type: "error", data: mcpError });
|
|
147
|
+
throw mcpError;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Initialize the MCP session and load tools/resources
|
|
153
|
+
*
|
|
154
|
+
* @return {Promise<Object>} Initialization result
|
|
155
|
+
*/
|
|
156
|
+
async initialize() {
|
|
157
|
+
if (!this.connected) {
|
|
158
|
+
throw new MCPError("Not connected to MCP server");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
// Load tools and resources
|
|
163
|
+
await Promise.all([this.loadTools(), this.loadResources()]);
|
|
164
|
+
|
|
165
|
+
const initResult = {
|
|
166
|
+
protocolVersion: "2025-06-18",
|
|
167
|
+
capabilities: {
|
|
168
|
+
tools: {},
|
|
169
|
+
resources: {},
|
|
170
|
+
prompts: {},
|
|
171
|
+
},
|
|
172
|
+
serverInfo: {
|
|
173
|
+
name: "WordPress MCP Server",
|
|
174
|
+
version: "1.0.0",
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
this.emit({ type: "initialized", data: initResult });
|
|
179
|
+
|
|
180
|
+
return initResult;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const mcpError =
|
|
183
|
+
error instanceof MCPError ? error : new MCPError(`Initialization failed: ${error.message}`);
|
|
184
|
+
this.emit({ type: "error", data: mcpError });
|
|
185
|
+
throw mcpError;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Normalize input schema to valid JSON Schema object
|
|
191
|
+
*
|
|
192
|
+
* @param {any} schema Raw input schema from MCP
|
|
193
|
+
* @return {Object} Valid JSON Schema object
|
|
194
|
+
*/
|
|
195
|
+
normalizeInputSchema(schema) {
|
|
196
|
+
if (
|
|
197
|
+
!schema ||
|
|
198
|
+
Array.isArray(schema) ||
|
|
199
|
+
typeof schema !== "object" ||
|
|
200
|
+
Object.keys(schema).length === 0
|
|
201
|
+
) {
|
|
202
|
+
return {
|
|
203
|
+
type: "object",
|
|
204
|
+
properties: {},
|
|
205
|
+
required: [],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
type: schema.type || "object",
|
|
211
|
+
properties: schema.properties || {},
|
|
212
|
+
required: Array.isArray(schema.required) ? schema.required : [],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Load available tools from the MCP server
|
|
218
|
+
*
|
|
219
|
+
* @return {Promise<void>}
|
|
220
|
+
*/
|
|
221
|
+
async loadTools() {
|
|
222
|
+
try {
|
|
223
|
+
const result = await this.client.listTools();
|
|
224
|
+
|
|
225
|
+
this.tools = result.tools.map((tool) => ({
|
|
226
|
+
name: tool.name,
|
|
227
|
+
description: tool.description || "",
|
|
228
|
+
inputSchema: this.normalizeInputSchema(tool.inputSchema),
|
|
229
|
+
annotations: tool.annotations || {},
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
this.emit({ type: "tools_updated", data: this.tools });
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error("Failed to load tools via SDK:", error);
|
|
235
|
+
this.tools = [];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Load available resources from the MCP server
|
|
241
|
+
*
|
|
242
|
+
* @return {Promise<void>}
|
|
243
|
+
*/
|
|
244
|
+
async loadResources() {
|
|
245
|
+
try {
|
|
246
|
+
const result = await this.client.listResources();
|
|
247
|
+
|
|
248
|
+
this.resources = result.resources.map((resource) => ({
|
|
249
|
+
uri: resource.uri,
|
|
250
|
+
name: resource.name || "",
|
|
251
|
+
description: resource.description,
|
|
252
|
+
mimeType: resource.mimeType,
|
|
253
|
+
}));
|
|
254
|
+
|
|
255
|
+
this.emit({ type: "resources_updated", data: this.resources });
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error("Failed to load resources via SDK:", error);
|
|
258
|
+
this.resources = [];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* List available tools
|
|
264
|
+
*
|
|
265
|
+
* @return {Promise<Array>} List of tools
|
|
266
|
+
*/
|
|
267
|
+
async listTools() {
|
|
268
|
+
if (!this.connected) {
|
|
269
|
+
throw new MCPError("Not connected to MCP server");
|
|
270
|
+
}
|
|
271
|
+
return this.tools;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Call a tool on the MCP server
|
|
276
|
+
*
|
|
277
|
+
* @param {string} name Tool name
|
|
278
|
+
* @param {Object} args Tool arguments
|
|
279
|
+
* @return {Promise<Object>} Tool result
|
|
280
|
+
*/
|
|
281
|
+
async callTool(name, args = {}) {
|
|
282
|
+
if (!this.connected) {
|
|
283
|
+
throw new MCPError("Not connected to MCP server");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const result = await this.client.callTool({ name, arguments: args });
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
content: Array.isArray(result.content) ? result.content : [],
|
|
291
|
+
isError: Boolean(result.isError),
|
|
292
|
+
meta: result.meta || {},
|
|
293
|
+
};
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error(`Tool "${name}" call failed:`, error);
|
|
296
|
+
const mcpError =
|
|
297
|
+
error instanceof MCPError ? error : new MCPError(`Tool call failed: ${error.message}`);
|
|
298
|
+
this.emit({ type: "error", data: mcpError });
|
|
299
|
+
throw mcpError;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* List available resources
|
|
305
|
+
*
|
|
306
|
+
* @return {Promise<Array>} List of resources
|
|
307
|
+
*/
|
|
308
|
+
async listResources() {
|
|
309
|
+
if (!this.connected) {
|
|
310
|
+
throw new MCPError("Not connected to MCP server");
|
|
311
|
+
}
|
|
312
|
+
return this.resources;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Read a resource from the MCP server
|
|
317
|
+
*
|
|
318
|
+
* @param {string} uri Resource URI
|
|
319
|
+
* @return {Promise<Object>} Resource content
|
|
320
|
+
*/
|
|
321
|
+
async readResource(uri) {
|
|
322
|
+
if (!this.connected) {
|
|
323
|
+
throw new MCPError("Not connected to MCP server");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
return await this.client.readResource({ uri });
|
|
328
|
+
} catch (error) {
|
|
329
|
+
console.error(`Resource "${uri}" read failed:`, error);
|
|
330
|
+
const mcpError =
|
|
331
|
+
error instanceof MCPError ? error : new MCPError(`Resource read failed: ${error.message}`);
|
|
332
|
+
this.emit({ type: "error", data: mcpError });
|
|
333
|
+
throw mcpError;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Disconnect from the MCP server
|
|
339
|
+
*
|
|
340
|
+
* @return {Promise<void>}
|
|
341
|
+
*/
|
|
342
|
+
async disconnect() {
|
|
343
|
+
try {
|
|
344
|
+
if (this.transport) {
|
|
345
|
+
await this.client.close();
|
|
346
|
+
this.transport = null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
this.connected = false;
|
|
350
|
+
this.tools = [];
|
|
351
|
+
this.resources = [];
|
|
352
|
+
this.emit({ type: "disconnected" });
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.error("Error during SDK disconnect:", error);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Check if connected to MCP server
|
|
360
|
+
*
|
|
361
|
+
* @return {boolean} Connection status
|
|
362
|
+
*/
|
|
363
|
+
isConnected() {
|
|
364
|
+
return this.connected;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Get cached tools
|
|
369
|
+
*
|
|
370
|
+
* @return {Array} List of tools
|
|
371
|
+
*/
|
|
372
|
+
getTools() {
|
|
373
|
+
return [...this.tools];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Get cached resources
|
|
378
|
+
*
|
|
379
|
+
* @return {Array} List of resources
|
|
380
|
+
*/
|
|
381
|
+
getResources() {
|
|
382
|
+
return [...this.resources];
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Check if a tool is read-only
|
|
387
|
+
*
|
|
388
|
+
* @param {string} toolName Tool name to check
|
|
389
|
+
* @return {boolean} True if read-only
|
|
390
|
+
*/
|
|
391
|
+
isToolReadOnly(toolName) {
|
|
392
|
+
const tool = this.tools.find((t) => t.name === toolName);
|
|
393
|
+
if (!tool) {
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
return tool.annotations?.readonly === true || tool.annotations?.readOnlyHint === true;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Convert all tools to OpenAI functions format
|
|
401
|
+
*
|
|
402
|
+
* @return {Array} OpenAI tools array
|
|
403
|
+
*/
|
|
404
|
+
getToolsForOpenAI() {
|
|
405
|
+
return this.tools.map((tool) => {
|
|
406
|
+
const parameters = this.normalizeInputSchema(tool.inputSchema);
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
type: "function",
|
|
410
|
+
function: {
|
|
411
|
+
name: tool.name,
|
|
412
|
+
description: tool.description || "",
|
|
413
|
+
parameters,
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Create a new MCP client instance
|
|
422
|
+
*
|
|
423
|
+
* @param {Object} options Configuration options
|
|
424
|
+
* @return {WordPressMCPClient} New client instance
|
|
425
|
+
*/
|
|
426
|
+
export const createMCPClient = (options = {}) => {
|
|
427
|
+
return new WordPressMCPClient(options);
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// Default singleton instance for backwards compatibility
|
|
431
|
+
export const mcpClient = new WordPressMCPClient();
|
|
432
|
+
|
|
433
|
+
export default mcpClient;
|